1 /* $OpenBSD: sypwr.c,v 1.5 2021/10/24 17:52:27 mpi 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 const 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
sypwr_match(struct device * parent,void * match,void * aux)62 sypwr_match(struct device *parent, void *match, void *aux)
63 {
64 struct i2c_attach_args *ia = aux;
65
66 return (strcmp(ia->ia_name, "silergy,sy8106a") == 0);
67 }
68
69 void
sypwr_attach(struct device * parent,struct device * self,void * aux)70 sypwr_attach(struct device *parent, struct device *self, void *aux)
71 {
72 struct sypwr_softc *sc = (struct sypwr_softc *)self;
73 struct i2c_attach_args *ia = aux;
74 int node = *(int *)ia->ia_cookie;
75 uint8_t reg;
76
77 sc->sc_tag = ia->ia_tag;
78 sc->sc_addr = ia->ia_addr;
79
80 sc->sc_fixed_microvolt =
81 OF_getpropint(node, "silergy,fixed-microvolt", 0);
82
83 /*
84 * Only register the regulator if it is under I2C control
85 * (i.e. initialized by the firmware) or if the device tree
86 * specifies its fixed voltage. Otherwise we have no idea
87 * what the current output voltage is, which will confuse the
88 * regulator framework.
89 */
90 reg = sypwr_read(sc, SY8106A_VOUT1_SEL);
91 if (reg & SY8106A_VOUT1_SEL_I2C || sc->sc_fixed_microvolt != 0) {
92 uint32_t voltage;
93
94 voltage = sypwr_get_voltage(sc);
95 printf(": %d.%02d VDC", voltage / 1000000,
96 (voltage % 1000000) / 10000);
97
98 sc->sc_rd.rd_node = node;
99 sc->sc_rd.rd_cookie = sc;
100 sc->sc_rd.rd_get_voltage = sypwr_get_voltage;
101 sc->sc_rd.rd_set_voltage = sypwr_set_voltage;
102 regulator_register(&sc->sc_rd);
103 }
104
105 printf("\n");
106 }
107
108 int
sypwr_activate(struct device * self,int act)109 sypwr_activate(struct device *self, int act)
110 {
111 struct sypwr_softc *sc = (struct sypwr_softc *)self;
112 uint8_t reg;
113
114 switch (act) {
115 case DVACT_POWERDOWN:
116 /*
117 * Restore fixed voltage otherwise we might hang after
118 * a warm reset.
119 */
120 if (sc->sc_fixed_microvolt != 0) {
121 reg = sypwr_read(sc, SY8106A_VOUT1_SEL);
122 reg &= ~SY8106A_VOUT1_SEL_I2C;
123 sypwr_write(sc, SY8106A_VOUT1_SEL, reg);
124 }
125 break;
126 }
127
128 return 0;
129 }
130
131 uint8_t
sypwr_read(struct sypwr_softc * sc,int reg)132 sypwr_read(struct sypwr_softc *sc, int reg)
133 {
134 uint8_t cmd = reg;
135 uint8_t val;
136 int error;
137
138 iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
139 error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
140 &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL);
141 iic_release_bus(sc->sc_tag, I2C_F_POLL);
142
143 if (error) {
144 printf("error %d\n", error);
145 printf("%s: can't read register 0x%02x\n",
146 sc->sc_dev.dv_xname, reg);
147 val = 0xff;
148 }
149
150 return val;
151 }
152
153 void
sypwr_write(struct sypwr_softc * sc,int reg,uint8_t val)154 sypwr_write(struct sypwr_softc *sc, int reg, uint8_t val)
155 {
156 uint8_t cmd = reg;
157 int error;
158
159 iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
160 error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
161 &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL);
162 iic_release_bus(sc->sc_tag, I2C_F_POLL);
163
164 if (error) {
165 printf("%s: can't write register 0x%02x\n",
166 sc->sc_dev.dv_xname, reg);
167 }
168 }
169
170 uint32_t
sypwr_get_voltage(void * cookie)171 sypwr_get_voltage(void *cookie)
172 {
173 struct sypwr_softc *sc = cookie;
174 uint8_t value;
175
176 value = sypwr_read(sc, SY8106A_VOUT1_SEL);
177 if (value & SY8106A_VOUT1_SEL_I2C)
178 return 680000 + (value & SY8106A_VOUT1_SEL_MASK) * 10000;
179 else
180 return sc->sc_fixed_microvolt;
181 }
182
183 int
sypwr_set_voltage(void * cookie,uint32_t voltage)184 sypwr_set_voltage(void *cookie, uint32_t voltage)
185 {
186 struct sypwr_softc *sc = cookie;
187 uint8_t value;
188
189 if (voltage < 680000 || voltage > 1950000)
190 return EINVAL;
191
192 value = (voltage - 680000) / 10000;
193 sypwr_write(sc, SY8106A_VOUT1_SEL, value | SY8106A_VOUT1_SEL_I2C);
194 return 0;
195 }
196