1 /* $OpenBSD: sypwr.c,v 1.3 2019/09/29 13:27:48 kettenis Exp $ */ 2 /* 3 * Copyright (c) 2017 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 #include <sys/malloc.h> 22 23 #include <dev/ofw/openfirm.h> 24 #include <dev/ofw/ofw_regulator.h> 25 #include <dev/ofw/fdt.h> 26 27 #include <dev/i2c/i2cvar.h> 28 29 #define SY8106A_VOUT1_SEL 0x01 30 #define SY8106A_VOUT1_SEL_I2C (1 << 7) 31 #define SY8106A_VOUT1_SEL_MASK 0x7f 32 33 struct sypwr_softc { 34 struct device sc_dev; 35 i2c_tag_t sc_tag; 36 i2c_addr_t sc_addr; 37 38 uint32_t sc_fixed_microvolt; 39 40 struct regulator_device sc_rd; 41 }; 42 43 int sypwr_match(struct device *, void *, void *); 44 void sypwr_attach(struct device *, struct device *, void *); 45 int sypwr_activate(struct device *, int); 46 47 struct cfattach sypwr_ca = { 48 sizeof(struct sypwr_softc), sypwr_match, sypwr_attach, 49 NULL, sypwr_activate 50 }; 51 52 struct cfdriver sypwr_cd = { 53 NULL, "sypwr", DV_DULL 54 }; 55 56 uint8_t sypwr_read(struct sypwr_softc *, int); 57 void sypwr_write(struct sypwr_softc *, int, uint8_t); 58 uint32_t sypwr_get_voltage(void *); 59 int sypwr_set_voltage(void *, uint32_t); 60 61 int 62 sypwr_match(struct device *parent, void *match, void *aux) 63 { 64 struct i2c_attach_args *ia = aux; 65 int node = *(int *)ia->ia_cookie; 66 67 return (OF_is_compatible(node, "silergy,sy8106a")); 68 } 69 70 void 71 sypwr_attach(struct device *parent, struct device *self, void *aux) 72 { 73 struct sypwr_softc *sc = (struct sypwr_softc *)self; 74 struct i2c_attach_args *ia = aux; 75 int node = *(int *)ia->ia_cookie; 76 uint8_t reg; 77 78 sc->sc_tag = ia->ia_tag; 79 sc->sc_addr = ia->ia_addr; 80 81 sc->sc_fixed_microvolt = 82 OF_getpropint(node, "silergy,fixed-microvolt", 0); 83 84 /* 85 * Only register the regulator if it is under I2C control 86 * (i.e. initialized by the firmware) or if the device tree 87 * specifies its fixed voltage. Otherwise we have no idea 88 * what the current output voltage is, which will confuse the 89 * regulator framework. 90 */ 91 reg = sypwr_read(sc, SY8106A_VOUT1_SEL); 92 if (reg & SY8106A_VOUT1_SEL_I2C || sc->sc_fixed_microvolt != 0) { 93 uint32_t voltage; 94 95 voltage = sypwr_get_voltage(sc); 96 printf(": %d.%02d VDC", voltage / 1000000, 97 (voltage % 1000000) / 10000); 98 99 sc->sc_rd.rd_node = node; 100 sc->sc_rd.rd_cookie = sc; 101 sc->sc_rd.rd_get_voltage = sypwr_get_voltage; 102 sc->sc_rd.rd_set_voltage = sypwr_set_voltage; 103 regulator_register(&sc->sc_rd); 104 } 105 106 printf("\n"); 107 } 108 109 int 110 sypwr_activate(struct device *self, int act) 111 { 112 struct sypwr_softc *sc = (struct sypwr_softc *)self; 113 uint8_t reg; 114 115 switch (act) { 116 case DVACT_POWERDOWN: 117 /* 118 * Restore fixed voltage otherwise we might hang after 119 * a warm reset. 120 */ 121 if (sc->sc_fixed_microvolt != 0) { 122 reg = sypwr_read(sc, SY8106A_VOUT1_SEL); 123 reg &= ~SY8106A_VOUT1_SEL_I2C; 124 sypwr_write(sc, SY8106A_VOUT1_SEL, reg); 125 } 126 break; 127 } 128 129 return 0; 130 } 131 132 uint8_t 133 sypwr_read(struct sypwr_softc *sc, int reg) 134 { 135 uint8_t cmd = reg; 136 uint8_t val; 137 int error; 138 139 iic_acquire_bus(sc->sc_tag, I2C_F_POLL); 140 error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, 141 &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL); 142 iic_release_bus(sc->sc_tag, I2C_F_POLL); 143 144 if (error) { 145 printf("error %d\n", error); 146 printf("%s: can't read register 0x%02x\n", 147 sc->sc_dev.dv_xname, reg); 148 val = 0xff; 149 } 150 151 return val; 152 } 153 154 void 155 sypwr_write(struct sypwr_softc *sc, int reg, uint8_t val) 156 { 157 uint8_t cmd = reg; 158 int error; 159 160 iic_acquire_bus(sc->sc_tag, I2C_F_POLL); 161 error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr, 162 &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL); 163 iic_release_bus(sc->sc_tag, I2C_F_POLL); 164 165 if (error) { 166 printf("%s: can't write register 0x%02x\n", 167 sc->sc_dev.dv_xname, reg); 168 } 169 } 170 171 uint32_t 172 sypwr_get_voltage(void *cookie) 173 { 174 struct sypwr_softc *sc = cookie; 175 uint8_t value; 176 177 value = sypwr_read(sc, SY8106A_VOUT1_SEL); 178 if (value & SY8106A_VOUT1_SEL_I2C) 179 return 680000 + (value & SY8106A_VOUT1_SEL_MASK) * 10000; 180 else 181 return sc->sc_fixed_microvolt; 182 } 183 184 int 185 sypwr_set_voltage(void *cookie, uint32_t voltage) 186 { 187 struct sypwr_softc *sc = cookie; 188 uint8_t value; 189 190 if (voltage < 680000 || voltage > 1950000) 191 return EINVAL; 192 193 value = (voltage - 680000) / 10000; 194 sypwr_write(sc, SY8106A_VOUT1_SEL, value | SY8106A_VOUT1_SEL_I2C); 195 return 0; 196 } 197