xref: /openbsd/sys/arch/loongson/dev/stsec.c (revision 4cfece93)
1 /*	$OpenBSD: stsec.c,v 1.5 2016/12/05 15:04:15 fcambus Exp $	*/
2 
3 /*
4  * Copyright (c) 2010 Miodrag Vallat.
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 /*
20  * Gdium ST7 Embedded Controller I2C interface
21  */
22 
23 #include <sys/param.h>
24 #include <sys/kernel.h>
25 #include <sys/systm.h>
26 #include <sys/device.h>
27 #include <sys/sensors.h>
28 
29 #include <machine/apmvar.h>
30 #include <dev/i2c/i2cvar.h>
31 
32 #include "apm.h"
33 
34 extern int gdium_revision;
35 
36 #define	ST7_VERSION	0x00	/* only on later mobos */
37 
38 #define	ST7_STATUS	0x00
39 #define	STS_LID_CLOSED		0x01
40 #define	STS_POWER_BTN_DOWN	0x02
41 #define	STS_BATTERY_PRESENT	0x04	/* not available on old mobo */
42 #define	STS_POWER_AVAILABLE	0x08
43 #define	STS_WAVELAN_BTN_DOWN	0x10	/* ``enable'' on old mobo */
44 #define	STS_AC_AVAILABLE	0x20
45 #define	ST7_CONTROL	0x01
46 #define	STC_DDR_CLOCK		0x01
47 #define	STC_CHARGE_LED_LIT	0x02
48 #define	STC_BEEP		0x04
49 #define	STC_DDR_POWER		0x08
50 #define	STC_TRICKLE		0x10	/* trickle charge rate */
51 #define	STC_RADIO_ENABLE	0x20	/* enable wavelan rf, later mobos */
52 #define	STC_MAIN_POWER		0x40
53 #define	STC_CHARGE_ENABLE	0x80
54 #define	ST7_BATTERY_L	0x02
55 #define	ST7_BATTERY_H	0x03
56 #define	STB_VALUE(h,l)	(((h) << 2) | ((l) & 0x03))
57 #define	ST7_SIGNATURE	0x04
58 #define	STSIG_EC_CONTROL	0x00
59 #define	STSIG_OS_CONTROL	0xae
60 /* rough battery operating state limits */
61 #define STSEC_BAT_MIN_VOLT	7000000	/* 7V */
62 #define STSEC_BAT_MAX_VOLT	8000000	/* 8V */
63 
64 const struct {
65 	const char *desc;
66 	enum sensor_type type;
67 } stsec_sensors_template[] = {
68 #define	STSEC_SENSOR_AC_PRESENCE	0
69 	{
70 		.desc = "AC present",
71 		.type = SENSOR_INDICATOR,
72 	},
73 #define	STSEC_SENSOR_BATTERY_PRESENCE	1
74 	{
75 		.desc = "Battery present",
76 		.type = SENSOR_INDICATOR,
77 	},
78 #define	STSEC_SENSOR_BATTERY_STATE	2
79 	{
80 		.desc = "Battery charging",
81 		.type = SENSOR_INDICATOR,
82 	},
83 #define	STSEC_SENSOR_BATTERY_VOLTAGE	3
84 	{
85 		.desc = "Battery voltage",
86 		.type = SENSOR_VOLTS_DC,
87 	}
88 };
89 
90 struct stsec_softc {
91 	struct device		 sc_dev;
92 	i2c_tag_t		 sc_tag;
93 	i2c_addr_t		 sc_addr;
94 	uint			 sc_base;
95 
96 	struct ksensor		 sc_sensors[nitems(stsec_sensors_template)];
97 	struct ksensordev	 sc_sensordev;
98 	struct sensor_task	*sc_sensors_update_task;
99 };
100 
101 int	stsec_match(struct device *, void *, void *);
102 void	stsec_attach(struct device *, struct device *, void *);
103 
104 const struct cfattach stsec_ca = {
105 	sizeof(struct stsec_softc), stsec_match, stsec_attach
106 };
107 
108 struct cfdriver stsec_cd = {
109 	NULL, "stsec", DV_DULL
110 };
111 
112 int	stsec_apminfo(struct apm_power_info *);
113 int	stsec_read(struct stsec_softc *, uint, int *);
114 int	stsec_write(struct stsec_softc *, uint, int);
115 void	stsec_sensors_update(void *);
116 
117 #if NAPM > 0
118 struct apm_power_info stsec_apmdata;
119 const char *stsec_batstate[] = {
120 	"high",
121 	"low",
122 	"critical",
123 	"charging",
124 	"unknown"
125 };
126 #define BATTERY_STRING(x) ((x) < nitems(stsec_batstate) ? \
127 	stsec_batstate[x] : stsec_batstate[4])
128 #endif
129 
130 int
131 stsec_match(struct device *parent, void *vcf, void *aux)
132 {
133 	struct i2c_attach_args *ia = (struct i2c_attach_args *)aux;
134 	struct cfdata *cf = (struct cfdata *)vcf;
135 
136 	return strcmp(ia->ia_name, cf->cf_driver->cd_name) == 0;
137 }
138 
139 void
140 stsec_attach(struct device *parent, struct device *self, void *aux)
141 {
142 	struct stsec_softc *sc = (struct stsec_softc *)self;
143 	struct i2c_attach_args *ia = (struct i2c_attach_args *)aux;
144 	int rev, sig;
145 	int rc;
146 	uint i;
147 
148 	sc->sc_tag = ia->ia_tag;
149 	sc->sc_addr = ia->ia_addr;
150 
151 	/*
152 	 * Figure out where to get our information, and display microcode
153 	 * version for geek value if available.
154 	 */
155 
156 	sc->sc_base = 0;
157 	switch (gdium_revision) {
158 	case 0:
159 		break;
160 	default:
161 		/* read version before sc_base is set */
162 		rc = stsec_read(sc, ST7_VERSION, &rev);
163 		if (rc != 0) {
164 			printf(": can't read microcode revision\n");
165 			return;
166 		}
167 		printf(": revision %d.%d", (rev >> 4) & 0x0f, rev & 0x0f);
168 		sc->sc_base = ST7_VERSION + 1;
169 		break;
170 	}
171 
172 	printf("\n");
173 
174 	/*
175 	 * Better trust the ST7 firmware to control charge operation.
176 	 */
177 
178 	rc = stsec_read(sc, ST7_SIGNATURE, &sig);
179 	if (rc != 0) {
180 		printf("%s: can't verify charge policy\n", self->dv_xname);
181 		/* not fatal */
182 	} else {
183 		if (sig != STSIG_EC_CONTROL)
184 			stsec_write(sc, ST7_SIGNATURE, STSIG_EC_CONTROL);
185 	}
186 
187 	/*
188 	 * Setup sensors. We use a quite short refresh interval to react
189 	 * quickly enough to button presses.
190 	 * XXX but we don't do anything on lid or button presses... yet
191 	 */
192 
193 	strlcpy(sc->sc_sensordev.xname, self->dv_xname,
194 	    sizeof(sc->sc_sensordev.xname));
195 	sc->sc_sensors_update_task =
196 	    sensor_task_register(sc, stsec_sensors_update, 2);
197 	if (sc->sc_sensors_update_task == NULL) {
198 		printf("%s: can't initialize refresh task\n", self->dv_xname);
199 		return;
200 	}
201 
202 	for (i = 0; i < nitems(sc->sc_sensors); i++) {
203 		sc->sc_sensors[i].type = stsec_sensors_template[i].type;
204 		strlcpy(sc->sc_sensors[i].desc, stsec_sensors_template[i].desc,
205 		    sizeof(sc->sc_sensors[i].desc));
206 		sensor_attach(&sc->sc_sensordev, &sc->sc_sensors[i]);
207 	}
208 	sensordev_install(&sc->sc_sensordev);
209 #if NAPM > 0
210 	/* make sure we have the apm state initialized before apm attaches */
211 	stsec_sensors_update(sc);
212 	apm_setinfohook(stsec_apminfo);
213 #endif
214 }
215 
216 int
217 stsec_read(struct stsec_softc *sc, uint reg, int *value)
218 {
219 	uint8_t regno, data;
220 	int rc;
221 
222 	regno = sc->sc_base + reg;
223 	iic_acquire_bus(sc->sc_tag, 0);
224 	rc = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
225 	    &regno, sizeof regno, &data, sizeof data, 0);
226 	iic_release_bus(sc->sc_tag, 0);
227 
228 	if (rc == 0)
229 		*value = (int)data;
230 	return rc;
231 }
232 
233 int
234 stsec_write(struct stsec_softc *sc, uint reg, int val)
235 {
236 	uint8_t regno, data[1];
237 	int rc;
238 
239 	regno = sc->sc_base + reg;
240 	data[0] = (uint8_t)val;
241 	iic_acquire_bus(sc->sc_tag, 0);
242 	rc = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
243 	    &regno, sizeof regno, data, sizeof data[0], 0);
244 	iic_release_bus(sc->sc_tag, 0);
245 	return rc;
246 }
247 
248 void
249 stsec_sensors_update(void *vsc)
250 {
251 	struct stsec_softc *sc = (struct stsec_softc *)vsc;
252 	int status, control, batl, bath;
253 	ulong batuv;
254 	struct ksensor *ks;
255 	uint i;
256 #if NAPM > 0
257 	struct apm_power_info old;
258 	uint cap_pct;
259 #endif
260 
261 	for (i = 0; i < nitems(sc->sc_sensors); i++)
262 		sc->sc_sensors[i].flags |= SENSOR_FINVALID;
263 
264 	if (stsec_read(sc, ST7_STATUS, &status) != 0 ||
265 	    stsec_read(sc, ST7_CONTROL, &control) != 0 ||
266 	    stsec_read(sc, ST7_BATTERY_L, &batl) != 0 ||
267 	    stsec_read(sc, ST7_BATTERY_H, &bath) != 0)
268 		return;
269 
270 	/*
271 	 * Battery voltage is in 10/1024V units, in the 0-1023 range.
272 	 */
273 	batuv = ((ulong)STB_VALUE(bath, batl) * 10 * 1000000) / 1024;
274 
275 	ks = &sc->sc_sensors[STSEC_SENSOR_AC_PRESENCE];
276 	ks->value = !!ISSET(status, STS_AC_AVAILABLE);
277 	ks->flags &= ~SENSOR_FINVALID;
278 
279 	/*
280 	 * Old mobo design does not have a battery presence bit; the Linux
281 	 * code decides there is no battery if the reported battery voltage
282 	 * is too low, we'll do the same.
283 	 */
284 	ks = &sc->sc_sensors[STSEC_SENSOR_BATTERY_PRESENCE];
285 	switch (gdium_revision) {
286 	case 0:
287 		if (ISSET(status, STS_AC_AVAILABLE))
288 			ks->value = batuv > 500000;
289 		else
290 			ks->value = 1;
291 		break;
292 	default:
293 		ks->value = !!ISSET(status, STS_BATTERY_PRESENT);
294 		break;
295 	}
296 	ks->flags &= ~SENSOR_FINVALID;
297 
298 	ks = &sc->sc_sensors[STSEC_SENSOR_BATTERY_STATE];
299 	ks->value = !!ISSET(control, STC_CHARGE_ENABLE);
300 	ks->flags &= ~SENSOR_FINVALID;
301 
302 	ks = &sc->sc_sensors[STSEC_SENSOR_BATTERY_VOLTAGE];
303 	ks->value = (int64_t)batuv;
304 	ks->flags &= ~SENSOR_FINVALID;
305 
306 #if NAPM > 0
307 	bcopy(&stsec_apmdata, &old, sizeof(old));
308 
309 	if (batuv < STSEC_BAT_MIN_VOLT)
310 		batuv = STSEC_BAT_MIN_VOLT;
311 	else if (batuv > STSEC_BAT_MAX_VOLT)
312 		batuv = STSEC_BAT_MAX_VOLT;
313 	cap_pct = (batuv - STSEC_BAT_MIN_VOLT) * 100 / (STSEC_BAT_MAX_VOLT -
314 	    STSEC_BAT_MIN_VOLT);
315 	stsec_apmdata.battery_life = cap_pct;
316 
317 	stsec_apmdata.ac_state = ISSET(status, STS_AC_AVAILABLE) ? APM_AC_ON :
318 	    APM_AC_OFF;
319 	if (!sc->sc_sensors[STSEC_SENSOR_BATTERY_PRESENCE].value) {
320 		stsec_apmdata.battery_state = APM_BATTERY_ABSENT;
321 		stsec_apmdata.minutes_left = 0;
322 		stsec_apmdata.battery_life = 0;
323 	} else {
324 		if (ISSET(control, STC_CHARGE_ENABLE))
325 			stsec_apmdata.battery_state = APM_BATT_CHARGING;
326 		else if (cap_pct > 50)
327 			stsec_apmdata.battery_state = APM_BATT_HIGH;
328 		else if (cap_pct > 25)
329 			stsec_apmdata.battery_state = APM_BATT_LOW;
330 		else
331 			stsec_apmdata.battery_state = APM_BATT_CRITICAL;
332 
333 		stsec_apmdata.minutes_left = -1; /* unknown */
334 	}
335 	if (old.ac_state != stsec_apmdata.ac_state)
336 		apm_record_event(APM_POWER_CHANGE, "AC power",
337 			stsec_apmdata.ac_state ? "restored" : "lost");
338 	if (old.battery_state != stsec_apmdata.battery_state)
339 		apm_record_event(APM_POWER_CHANGE, "battery",
340 		    BATTERY_STRING(stsec_apmdata.battery_state));
341 #endif
342 }
343 
344 #if NAPM > 0
345 int
346 stsec_apminfo(struct apm_power_info *info)
347 {
348 	 bcopy(&stsec_apmdata, info, sizeof(struct apm_power_info));
349 	 return 0;
350 }
351 #endif
352