tipmic.c revision 1.1
1/*	$OpenBSD: tipmic.c,v 1.1 2018/05/20 19:30:21 kettenis Exp $	*/
2/*
3 * Copyright (c) 2018 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/kernel.h>
22#include <sys/malloc.h>
23
24#include <dev/acpi/acpireg.h>
25#include <dev/acpi/acpivar.h>
26#include <dev/acpi/acpidev.h>
27#include <dev/acpi/amltypes.h>
28#include <dev/acpi/dsdt.h>
29
30#include <dev/i2c/i2cvar.h>
31
32#define TIPMIC_INTR_STAT		0x01
33#define  TIPMIC_INTR_STAT_ADC		(1 << 2)
34#define TIPMIC_INTR_MASK		0x02
35#define  TIPMIC_INTR_MASK_ADC		(1 << 2)
36#define  TIPMIC_INTR_MASK_ALL		0xff
37#define TIPMIC_ADC_CTRL			0x50
38#define  TIPMIC_ADC_CTRL_START		(1 << 0)
39#define  TIPMIC_ADC_CTRL_CH_MASK	(3 << 1)
40#define  TIPMIC_ADC_CTRL_CH_PMICTEMP	(1 << 1)
41#define  TIPMIC_ADC_CTRL_CH_BATTEMP	(2 << 1)
42#define  TIPMIC_ADC_CTRL_CH_SYSTEMP	(3 << 1)
43#define  TIPMIC_ADC_CTRL_EN		(1 << 5)
44#define TIPMIC_PMICTEMP_HI		0x56
45#define TIPMIC_PMICTEMP_LO		0x57
46#define TIPMIC_BATTEMP_HI		0x58
47#define TIPMIC_BATTEMP_LO		0x59
48#define TIPMIC_SYSTEMP_HI		0x5a
49#define TIPMIC_SYSTEMP_LO		0x5b
50
51#define TIPMIC_REGIONSPACE_THERMAL	0x8c
52
53struct acpi_lpat {
54	int32_t temp;
55	int32_t raw;
56};
57
58struct tipmic_softc {
59	struct device	sc_dev;
60	struct acpi_softc *sc_acpi;
61	struct aml_node *sc_node;
62	i2c_tag_t	sc_tag;
63	i2c_addr_t	sc_addr;
64
65	void		*sc_ih;
66	volatile int	sc_stat_adc;
67
68	struct acpi_lpat *sc_lpat;
69	size_t		sc_lpat_len;
70
71	struct acpi_gpio sc_gpio;
72};
73
74int	tipmic_match(struct device *, void *, void *);
75void	tipmic_attach(struct device *, struct device *, void *);
76
77struct cfattach tipmic_ca = {
78	sizeof(struct tipmic_softc), tipmic_match, tipmic_attach
79};
80
81struct cfdriver tipmic_cd = {
82	NULL, "tipmic", DV_DULL
83};
84
85uint8_t	tipmic_read_1(struct tipmic_softc *, uint8_t, int);
86void	tipmic_write_1(struct tipmic_softc *, uint8_t, uint8_t, int);
87int	tipmic_intr(void *);
88void	tipmic_get_lpat(struct tipmic_softc *);
89int32_t	tipmic_raw_to_temp(struct tipmic_softc *, int32_t);
90int	tipmic_thermal_opreg_handler(void *, int, uint64_t, int, uint64_t *);
91int	tipmic_read_pin(void *, int);
92void	tipmic_write_pin(void *, int, int);
93
94int
95tipmic_match(struct device *parent, void *match, void *aux)
96{
97	struct i2c_attach_args *ia = aux;
98
99	return (strcmp(ia->ia_name, "INT33F5") == 0);
100}
101
102void
103tipmic_attach(struct device *parent, struct device *self, void *aux)
104{
105	struct tipmic_softc *sc = (struct tipmic_softc *)self;
106	struct i2c_attach_args *ia = aux;
107
108	sc->sc_tag = ia->ia_tag;
109	sc->sc_addr = ia->ia_addr;
110	sc->sc_acpi = acpi_softc;
111	sc->sc_node = ia->ia_cookie;
112
113	if (ia->ia_intr == NULL) {
114		printf(": no interrupt\n");
115		return;
116	}
117
118	/* Mask all interrupts before we install our interrupt handler. */
119	tipmic_write_1(sc, TIPMIC_INTR_MASK, TIPMIC_INTR_MASK_ALL, I2C_F_POLL);
120
121	printf(" %s", iic_intr_string(sc->sc_tag, ia->ia_intr));
122	sc->sc_ih = iic_intr_establish(sc->sc_tag, ia->ia_intr,
123	    IPL_BIO, tipmic_intr, sc, sc->sc_dev.dv_xname);
124	if (sc->sc_ih == NULL) {
125		printf(": can't establish interrupt\n");
126		return;
127	}
128
129	printf("\n");
130
131	tipmic_get_lpat(sc);
132	if (sc->sc_lpat == NULL)
133		return;
134
135	sc->sc_gpio.cookie = sc;
136	sc->sc_gpio.read_pin = tipmic_read_pin;
137	sc->sc_gpio.write_pin = tipmic_write_pin;
138	sc->sc_node->gpio = &sc->sc_gpio;
139	acpi_register_gpio(sc->sc_acpi, sc->sc_node);
140
141	/* Register OEM defined address space. */
142	aml_register_regionspace(sc->sc_node, TIPMIC_REGIONSPACE_THERMAL,
143	    sc, tipmic_thermal_opreg_handler);
144}
145
146uint8_t
147tipmic_read_1(struct tipmic_softc *sc, uint8_t reg, int flags)
148{
149	uint8_t val;
150	int error;
151
152	iic_acquire_bus(sc->sc_tag, flags);
153	error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
154	    &reg, sizeof(reg), &val, sizeof(val), flags);
155	iic_release_bus(sc->sc_tag, flags);
156
157	if (error) {
158		printf("%s: can't read register 0x%02x\n",
159		    sc->sc_dev.dv_xname, reg);
160		val = 0xff;
161	}
162
163	return val;
164}
165
166void
167tipmic_write_1(struct tipmic_softc *sc, uint8_t reg, uint8_t val, int flags)
168{
169	int error;
170
171	iic_acquire_bus(sc->sc_tag, flags);
172	error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
173	    &reg, sizeof(reg), &val, sizeof(val), flags);
174	iic_release_bus(sc->sc_tag, flags);
175
176	if (error) {
177		printf("%s: can't write register 0x%02x\n",
178		    sc->sc_dev.dv_xname, reg);
179	}
180}
181
182int
183tipmic_intr(void *arg)
184{
185	struct tipmic_softc *sc = arg;
186	int handled = 0;
187	uint8_t stat;
188
189	stat = tipmic_read_1(sc, TIPMIC_INTR_STAT, I2C_F_POLL);
190	tipmic_write_1(sc, TIPMIC_INTR_STAT, stat, I2C_F_POLL);
191	if (stat & TIPMIC_INTR_STAT_ADC) {
192		sc->sc_stat_adc = 1;
193		wakeup(&sc->sc_stat_adc);
194		handled = 1;
195	}
196
197	return handled;
198}
199
200void
201tipmic_get_lpat(struct tipmic_softc *sc)
202{
203	struct aml_value res;
204	int i;
205
206	if (aml_evalname(sc->sc_acpi, sc->sc_node, "LPAT", 0, NULL, &res))
207		return;
208	if (res.type != AML_OBJTYPE_PACKAGE)
209		goto out;
210	if (res.length < 4 || (res.length % 2) != 0)
211		goto out;
212
213	sc->sc_lpat_len = res.length / 2;
214	sc->sc_lpat = mallocarray(sc->sc_lpat_len, sizeof(struct acpi_lpat),
215	    M_DEVBUF, M_WAITOK);
216
217	for (i = 0; i < sc->sc_lpat_len; i++) {
218		sc->sc_lpat[i].temp = aml_val2int(res.v_package[2 * i]);
219		sc->sc_lpat[i].raw = aml_val2int(res.v_package[2 * i + 1]);
220	}
221
222out:
223	aml_freevalue(&res);
224}
225
226int32_t
227tipmic_raw_to_temp(struct tipmic_softc *sc, int32_t raw)
228{
229	struct acpi_lpat *lpat = sc->sc_lpat;
230	int32_t raw0, delta_raw;
231	int32_t temp0, delta_temp;
232	int i;
233
234	for (i = 1; i < sc->sc_lpat_len; i++) {
235		/* Coefficient can be positive or negative. */
236		if (raw >= lpat[i - 1].raw && raw <= lpat[i].raw)
237			break;
238		if (raw <= lpat[i - 1].raw && raw >= lpat[i].raw)
239			break;
240	}
241	if (i == sc->sc_lpat_len)
242		return -1;
243
244	raw0 = lpat[i - 1].raw;
245	temp0 = lpat[i - 1].temp;
246	delta_raw = lpat[i].raw - raw0;
247	delta_temp = lpat[i].temp - temp0;
248
249	return temp0 + (raw - raw0) * delta_temp / delta_raw;
250}
251
252struct tipmic_regmap {
253	uint8_t address;
254	uint8_t hi, lo;
255};
256
257struct tipmic_regmap tipmic_thermal_regmap[] = {
258	{ 0x18, TIPMIC_SYSTEMP_HI, TIPMIC_SYSTEMP_LO }
259};
260
261int
262tipmic_thermal_opreg_handler(void *cookie, int iodir, uint64_t address,
263    int size, uint64_t *value)
264{
265	struct tipmic_softc *sc = cookie;
266	int32_t temp;
267	uint16_t raw;
268	uint8_t hi, lo;
269	uint8_t reg;
270	int i, s;
271
272	/* Only allow 32-bit read access. */
273	if (size != 4 || iodir != ACPI_IOREAD)
274		return -1;
275
276	for (i = 0; i < nitems(tipmic_thermal_regmap); i++) {
277		if (address == tipmic_thermal_regmap[i].address)
278			break;
279	}
280	if (i == nitems(tipmic_thermal_regmap))
281		return -1;
282
283	/* Turn ADC on and select the appropriate channel. */
284	reg = tipmic_read_1(sc, TIPMIC_ADC_CTRL, 0);
285	reg |= TIPMIC_ADC_CTRL_EN;
286	tipmic_write_1(sc, TIPMIC_ADC_CTRL, reg, 0);
287	switch (tipmic_thermal_regmap[i].hi) {
288	case TIPMIC_SYSTEMP_HI:
289		reg |= TIPMIC_ADC_CTRL_CH_SYSTEMP;
290		break;
291	default:
292		panic("%s: unsupported channel", sc->sc_dev.dv_xname);
293	}
294	tipmic_write_1(sc, TIPMIC_ADC_CTRL, reg, 0);
295
296	/* Need to wait 50us before starting the conversion. */
297	delay(50);
298
299	/* Start conversion. */
300	sc->sc_stat_adc = 0;
301	reg |= TIPMIC_ADC_CTRL_START;
302	tipmic_write_1(sc, TIPMIC_ADC_CTRL, reg, 0);
303
304	/*
305	 * Block interrupts to prevent I2C access from the interrupt
306	 * handler during the completion of the write that unmasks the
307	 * ADC interrupt.
308	 */
309	s = splbio();
310	reg = tipmic_read_1(sc, TIPMIC_INTR_MASK, I2C_F_POLL);
311	reg &= ~TIPMIC_INTR_MASK_ADC;
312	tipmic_write_1(sc, TIPMIC_INTR_MASK, reg, I2C_F_POLL);
313	splx(s);
314
315	while (sc->sc_stat_adc == 0) {
316		if (tsleep(&sc->sc_stat_adc, PRIBIO, "tipmic", hz)) {
317			printf("%s: ADC timeout\n", sc->sc_dev.dv_xname);
318			break;
319		}
320	}
321
322	/* Mask ADC interrupt again. */
323	s = splbio();
324	reg = tipmic_read_1(sc, TIPMIC_INTR_MASK, I2C_F_POLL);
325	reg |= TIPMIC_INTR_MASK_ADC;
326	tipmic_write_1(sc, TIPMIC_INTR_MASK, reg, I2C_F_POLL);
327	splx(s);
328
329	hi = tipmic_thermal_regmap[i].hi;
330	lo = tipmic_thermal_regmap[i].lo;
331	raw = (tipmic_read_1(sc, hi, 0) & 0x03) << 8;
332	raw |= tipmic_read_1(sc, lo, 0);
333
334	/* Turn ADC off. */
335	reg = tipmic_read_1(sc, TIPMIC_ADC_CTRL, 0);
336	reg &= ~(TIPMIC_ADC_CTRL_EN | TIPMIC_ADC_CTRL_CH_MASK);
337	tipmic_write_1(sc, TIPMIC_ADC_CTRL, reg, 0);
338
339	temp = tipmic_raw_to_temp(sc, raw);
340	if (temp < 0)
341		return -1;
342
343	*value = temp;
344	return 0;
345}
346
347/*
348 * Allegdly the GPIOs are virtual and only there to deal with a
349 * limitation of Microsoft Windows.
350 */
351
352int
353tipmic_read_pin(void *cookie, int pin)
354{
355	return 0;
356}
357
358void
359tipmic_write_pin(void *cookie, int pin, int value)
360{
361}
362