1/*	$OpenBSD: ccpmic.c,v 1.3 2022/04/06 18:59:27 naddy 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 CCPMIC_GPIO0P0CTLO		0x2b
33#define CCPMIC_GPIO0P0CTLI		0x33
34#define CCPMIC_GPIO1P0CTLO		0x3b
35#define CCPMIC_GPIO1P0CTLI		0x43
36#define CCPMIC_GPIOPANELCTL		0x52
37#define  CCPMIC_GPIOCTLO_RVAL_2KUP	(1 << 1)
38#define  CCPMIC_GPIOCTLO_DRV_REN	(1 << 3)
39#define  CCPMIC_GPIOCTLO_DIR_OUT	(1 << 5)
40#define  CCPMIC_GPIOCTLI_VALUE		(1 << 0)
41
42#define CCPMIC_GPIOCTLO_INPUT \
43    (CCPMIC_GPIOCTLO_DRV_REN | CCPMIC_GPIOCTLO_RVAL_2KUP)
44#define CCPMIC_GPIOCTLO_OUTPUT \
45    (CCPMIC_GPIOCTLO_INPUT | CCPMIC_GPIOCTLO_DIR_OUT)
46
47#define CCPMIC_V1P8SX			0x5d
48#define CCPMIC_V1P2SX			0x61
49#define CCPMIC_V2P85SX			0x66
50#define  CCPMIC_PWR_ON			(1 << 0)
51#define  CCPMIC_PWR_SEL			(1 << 1)
52#define CCPMIC_SYS2_THRM_RSLT_H		0x78
53#define CCPMIC_SYS2_THRM_RSLT_L		0x79
54
55#define CCPMIC_REGIONSPACE_THERMAL	0x8c
56#define CCPMIC_REGIONSPACE_POWER	0x8d
57
58#define CCPMIC_NPINS			16
59
60struct acpi_lpat {
61	int32_t temp;
62	int32_t raw;
63};
64
65struct ccpmic_softc {
66	struct device	sc_dev;
67	struct acpi_softc *sc_acpi;
68	struct aml_node *sc_node;
69	i2c_tag_t	sc_tag;
70	i2c_addr_t	sc_addr;
71
72	struct acpi_lpat *sc_lpat;
73	size_t		sc_lpat_len;
74
75	struct acpi_gpio sc_gpio;
76};
77
78int	ccpmic_match(struct device *, void *, void *);
79void	ccpmic_attach(struct device *, struct device *, void *);
80
81const struct cfattach ccpmic_ca = {
82	sizeof(struct ccpmic_softc), ccpmic_match, ccpmic_attach
83};
84
85struct cfdriver ccpmic_cd = {
86	NULL, "ccpmic", DV_DULL
87};
88
89uint8_t	ccpmic_read_1(struct ccpmic_softc *, uint8_t, int);
90void	ccpmic_write_1(struct ccpmic_softc *, uint8_t, uint8_t, int);
91void	ccpmic_get_lpat(struct ccpmic_softc *);
92int32_t	ccpmic_raw_to_temp(struct ccpmic_softc *, int32_t);
93int	ccpmic_thermal_opreg_handler(void *, int, uint64_t, int, uint64_t *);
94int	ccpmic_power_opreg_handler(void *, int, uint64_t, int, uint64_t *);
95int	ccpmic_read_pin(void *, int);
96void	ccpmic_write_pin(void *, int, int);
97
98int
99ccpmic_match(struct device *parent, void *match, void *aux)
100{
101	struct i2c_attach_args *ia = aux;
102
103	return (strcmp(ia->ia_name, "INT33FD") == 0);
104}
105
106void
107ccpmic_attach(struct device *parent, struct device *self, void *aux)
108{
109	struct ccpmic_softc *sc = (struct ccpmic_softc *)self;
110	struct i2c_attach_args *ia = aux;
111
112	sc->sc_tag = ia->ia_tag;
113	sc->sc_addr = ia->ia_addr;
114	sc->sc_acpi = acpi_softc;
115	sc->sc_node = ia->ia_cookie;
116
117	printf("\n");
118
119	ccpmic_get_lpat(sc);
120	if (sc->sc_lpat == NULL)
121		return;
122
123	sc->sc_gpio.cookie = sc;
124	sc->sc_gpio.read_pin = ccpmic_read_pin;
125	sc->sc_gpio.write_pin = ccpmic_write_pin;
126	sc->sc_node->gpio = &sc->sc_gpio;
127	acpi_register_gpio(sc->sc_acpi, sc->sc_node);
128
129	/* Register OEM defined address space. */
130	aml_register_regionspace(sc->sc_node, CCPMIC_REGIONSPACE_THERMAL,
131	    sc, ccpmic_thermal_opreg_handler);
132	aml_register_regionspace(sc->sc_node, CCPMIC_REGIONSPACE_POWER,
133	    sc, ccpmic_power_opreg_handler);
134}
135
136uint8_t
137ccpmic_read_1(struct ccpmic_softc *sc, uint8_t reg, int flags)
138{
139	uint8_t val;
140	int error;
141
142	iic_acquire_bus(sc->sc_tag, flags);
143	error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
144	    &reg, sizeof(reg), &val, sizeof(val), flags);
145	iic_release_bus(sc->sc_tag, flags);
146
147	if (error) {
148		printf("%s: can't read register 0x%02x\n",
149		    sc->sc_dev.dv_xname, reg);
150		val = 0xff;
151	}
152
153	return val;
154}
155
156void
157ccpmic_write_1(struct ccpmic_softc *sc, uint8_t reg, uint8_t val, int flags)
158{
159	int error;
160
161	iic_acquire_bus(sc->sc_tag, flags);
162	error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
163	    &reg, sizeof(reg), &val, sizeof(val), flags);
164	iic_release_bus(sc->sc_tag, flags);
165
166	if (error) {
167		printf("%s: can't write register 0x%02x\n",
168		    sc->sc_dev.dv_xname, reg);
169	}
170}
171
172void
173ccpmic_get_lpat(struct ccpmic_softc *sc)
174{
175	struct aml_value res;
176	int i;
177
178	if (aml_evalname(sc->sc_acpi, sc->sc_node, "LPAT", 0, NULL, &res))
179		return;
180	if (res.type != AML_OBJTYPE_PACKAGE)
181		goto out;
182	if (res.length < 4 || (res.length % 2) != 0)
183		goto out;
184
185	sc->sc_lpat_len = res.length / 2;
186	sc->sc_lpat = mallocarray(sc->sc_lpat_len, sizeof(struct acpi_lpat),
187	    M_DEVBUF, M_WAITOK);
188
189	for (i = 0; i < sc->sc_lpat_len; i++) {
190		sc->sc_lpat[i].temp = aml_val2int(res.v_package[2 * i]);
191		sc->sc_lpat[i].raw = aml_val2int(res.v_package[2 * i + 1]);
192	}
193
194out:
195	aml_freevalue(&res);
196}
197
198int32_t
199ccpmic_raw_to_temp(struct ccpmic_softc *sc, int32_t raw)
200{
201	struct acpi_lpat *lpat = sc->sc_lpat;
202	int32_t raw0, delta_raw;
203	int32_t temp0, delta_temp;
204	int i;
205
206	for (i = 1; i < sc->sc_lpat_len; i++) {
207		/* Coefficient can be positive or negative. */
208		if (raw >= lpat[i - 1].raw && raw <= lpat[i].raw)
209			break;
210		if (raw <= lpat[i - 1].raw && raw >= lpat[i].raw)
211			break;
212	}
213	if (i == sc->sc_lpat_len)
214		return -1;
215
216	raw0 = lpat[i - 1].raw;
217	temp0 = lpat[i - 1].temp;
218	delta_raw = lpat[i].raw - raw0;
219	delta_temp = lpat[i].temp - temp0;
220
221	return temp0 + (raw - raw0) * delta_temp / delta_raw;
222}
223
224struct ccpmic_regmap {
225	uint8_t address;
226	uint8_t hi, lo;
227};
228
229struct ccpmic_regmap ccpmic_thermal_regmap[] = {
230	{ 0x18, CCPMIC_SYS2_THRM_RSLT_H, CCPMIC_SYS2_THRM_RSLT_L }, /* TMP2 */
231};
232
233int
234ccpmic_thermal_opreg_handler(void *cookie, int iodir, uint64_t address,
235    int size, uint64_t *value)
236{
237	struct ccpmic_softc *sc = cookie;
238	int32_t temp;
239	uint16_t raw;
240	uint8_t lo, hi;
241	int i;
242
243	/* Only allow 32-bit read access. */
244	if (size != 4 || iodir != ACPI_IOREAD)
245		return -1;
246
247	for (i = 0; i < nitems(ccpmic_thermal_regmap); i++) {
248		if (address == ccpmic_thermal_regmap[i].address)
249			break;
250	}
251	if (i == nitems(ccpmic_thermal_regmap)) {
252		printf("%s: addr 0x%02llx\n", __func__, address);
253		return -1;
254	}
255
256	lo = ccpmic_thermal_regmap[i].lo;
257	hi = ccpmic_thermal_regmap[i].hi;
258	raw = ccpmic_read_1(sc, lo, 0);
259	raw |= (ccpmic_read_1(sc, hi, 0) & 0x03) << 8;
260
261	temp = ccpmic_raw_to_temp(sc, raw);
262	if (temp < 0)
263		return -1;
264
265	*value = temp;
266	return 0;
267}
268
269struct ccpmic_regmap ccpmic_power_regmap[] = {
270	{ 0x24, CCPMIC_V2P85SX },	/* X285 */
271	{ 0x48, CCPMIC_V1P8SX },	/* V18X */
272	{ 0x50, CCPMIC_V1P2SX },	/* V12X */
273};
274
275int
276ccpmic_power_opreg_handler(void *cookie, int iodir, uint64_t address,
277    int size, uint64_t *value)
278{
279	struct ccpmic_softc *sc = cookie;
280	uint8_t reg, val;
281	int i;
282
283	/* Only allow 32-bit access. */
284	if (size != 4)
285		return -1;
286
287	for (i = 0; i < nitems(ccpmic_power_regmap); i++) {
288		if (address == ccpmic_power_regmap[i].address)
289			break;
290	}
291	if (i == nitems(ccpmic_power_regmap)) {
292		printf("%s: addr 0x%02llx\n", __func__, address);
293		return -1;
294	}
295
296	reg = ccpmic_power_regmap[i].hi;
297	val = ccpmic_read_1(sc, reg, 0);
298	if (iodir == ACPI_IOREAD) {
299		if ((val & CCPMIC_PWR_SEL) && (val & CCPMIC_PWR_ON))
300			*value = 1;
301		else
302			*value = 0;
303	} else {
304		if (*value)
305			val |= CCPMIC_PWR_ON;
306		else
307			val &= ~CCPMIC_PWR_ON;
308		ccpmic_write_1(sc, reg, val | CCPMIC_PWR_SEL, 0);
309	}
310
311	return 0;
312}
313
314/*
315 * We have 16 real GPIOs and a bunch of virtual ones.  The virtual
316 * ones are mostly there to deal with a limitation of Microsoft
317 * Windows.  We only implement the "panel" control GPIO, which
318 * actually maps onto a real GPIO.
319 */
320
321int
322ccpmic_read_pin(void *cookie, int pin)
323{
324	struct ccpmic_softc *sc = cookie;
325	uint8_t reg;
326
327	if (pin >= CCPMIC_NPINS)
328		return 0;
329
330	reg = ((pin < 8) ? CCPMIC_GPIO0P0CTLO : CCPMIC_GPIO1P0CTLO) + pin % 8;
331	ccpmic_write_1(sc, reg, CCPMIC_GPIOCTLO_INPUT, 0);
332	reg = ((pin < 8) ? CCPMIC_GPIO0P0CTLI : CCPMIC_GPIO1P0CTLI) + pin % 8;
333	return ccpmic_read_1(sc, reg, 0) & CCPMIC_GPIOCTLI_VALUE;
334}
335
336void
337ccpmic_write_pin(void *cookie, int pin, int value)
338{
339	struct ccpmic_softc *sc = cookie;
340	uint8_t reg;
341
342	if (pin == 0x5e) {
343		reg = CCPMIC_GPIOPANELCTL;
344		ccpmic_write_1(sc, reg, CCPMIC_GPIOCTLO_OUTPUT | !!value, 0);
345		return;
346	}
347
348	if (pin >= CCPMIC_NPINS)
349		return;
350
351	reg = ((pin < 8) ? CCPMIC_GPIO0P0CTLO : CCPMIC_GPIO1P0CTLO) + pin % 8;
352	ccpmic_write_1(sc, reg, CCPMIC_GPIOCTLO_OUTPUT | !!value, 0);
353}
354