summaryrefslogtreecommitdiffstats
path: root/sys/dev
diff options
context:
space:
mode:
authorkettenis <kettenis@openbsd.org>2018-05-20 19:30:21 +0000
committerkettenis <kettenis@openbsd.org>2018-05-20 19:30:21 +0000
commitad7ce60aa9ffa9b1263d76b66f9f176221d764db (patch)
tree60260fb6dcff25f9e68b9cd967c1378bb28ee963 /sys/dev
parentStash the syscall number in tf_err so it can be reported by the SPL check (diff)
downloadwireguard-openbsd-ad7ce60aa9ffa9b1263d76b66f9f176221d764db.tar.xz
wireguard-openbsd-ad7ce60aa9ffa9b1263d76b66f9f176221d764db.zip
Add tipmic(4), a driver for the "Intel" Dollar Cove TI PMIC. This is actually
a TI part (SND9039) but the datasheet is only available under NDA because it contains "Intel proprietary information". Initial implementation handles the thermal sensors, providing data to acpi(4) which is used by acpitz(4). Power management functionality will be added later. Disabled for now until some bugs in dwiic(4) are fixed.
Diffstat (limited to 'sys/dev')
-rw-r--r--sys/dev/acpi/files.acpi7
-rw-r--r--sys/dev/acpi/tipmic.c361
2 files changed, 367 insertions, 1 deletions
diff --git a/sys/dev/acpi/files.acpi b/sys/dev/acpi/files.acpi
index 310a9a88850..31afd975601 100644
--- a/sys/dev/acpi/files.acpi
+++ b/sys/dev/acpi/files.acpi
@@ -1,4 +1,4 @@
-# $OpenBSD: files.acpi,v 1.40 2018/03/27 21:11:16 kettenis Exp $
+# $OpenBSD: files.acpi,v 1.41 2018/05/20 19:30:21 kettenis Exp $
#
# Config file and device description for machine-independent ACPI code.
# Included by ports that need it.
@@ -127,6 +127,11 @@ device chvgpio
attach chvgpio at acpi
file dev/acpi/chvgpio.c chvgpio
+# "Intel" Dollar Cove TI PMIC
+device tipmic
+attach tipmic at i2c
+file dev/acpi/tipmic.c tipmic
+
# SD Host Controller
attach sdhc at acpi with sdhc_acpi
file dev/acpi/sdhc_acpi.c sdhc_acpi
diff --git a/sys/dev/acpi/tipmic.c b/sys/dev/acpi/tipmic.c
new file mode 100644
index 00000000000..d9ded2372fd
--- /dev/null
+++ b/sys/dev/acpi/tipmic.c
@@ -0,0 +1,361 @@
+/* $OpenBSD: tipmic.c,v 1.1 2018/05/20 19:30:21 kettenis Exp $ */
+/*
+ * Copyright (c) 2018 Mark Kettenis <kettenis@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/device.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+
+#include <dev/acpi/acpireg.h>
+#include <dev/acpi/acpivar.h>
+#include <dev/acpi/acpidev.h>
+#include <dev/acpi/amltypes.h>
+#include <dev/acpi/dsdt.h>
+
+#include <dev/i2c/i2cvar.h>
+
+#define TIPMIC_INTR_STAT 0x01
+#define TIPMIC_INTR_STAT_ADC (1 << 2)
+#define TIPMIC_INTR_MASK 0x02
+#define TIPMIC_INTR_MASK_ADC (1 << 2)
+#define TIPMIC_INTR_MASK_ALL 0xff
+#define TIPMIC_ADC_CTRL 0x50
+#define TIPMIC_ADC_CTRL_START (1 << 0)
+#define TIPMIC_ADC_CTRL_CH_MASK (3 << 1)
+#define TIPMIC_ADC_CTRL_CH_PMICTEMP (1 << 1)
+#define TIPMIC_ADC_CTRL_CH_BATTEMP (2 << 1)
+#define TIPMIC_ADC_CTRL_CH_SYSTEMP (3 << 1)
+#define TIPMIC_ADC_CTRL_EN (1 << 5)
+#define TIPMIC_PMICTEMP_HI 0x56
+#define TIPMIC_PMICTEMP_LO 0x57
+#define TIPMIC_BATTEMP_HI 0x58
+#define TIPMIC_BATTEMP_LO 0x59
+#define TIPMIC_SYSTEMP_HI 0x5a
+#define TIPMIC_SYSTEMP_LO 0x5b
+
+#define TIPMIC_REGIONSPACE_THERMAL 0x8c
+
+struct acpi_lpat {
+ int32_t temp;
+ int32_t raw;
+};
+
+struct tipmic_softc {
+ struct device sc_dev;
+ struct acpi_softc *sc_acpi;
+ struct aml_node *sc_node;
+ i2c_tag_t sc_tag;
+ i2c_addr_t sc_addr;
+
+ void *sc_ih;
+ volatile int sc_stat_adc;
+
+ struct acpi_lpat *sc_lpat;
+ size_t sc_lpat_len;
+
+ struct acpi_gpio sc_gpio;
+};
+
+int tipmic_match(struct device *, void *, void *);
+void tipmic_attach(struct device *, struct device *, void *);
+
+struct cfattach tipmic_ca = {
+ sizeof(struct tipmic_softc), tipmic_match, tipmic_attach
+};
+
+struct cfdriver tipmic_cd = {
+ NULL, "tipmic", DV_DULL
+};
+
+uint8_t tipmic_read_1(struct tipmic_softc *, uint8_t, int);
+void tipmic_write_1(struct tipmic_softc *, uint8_t, uint8_t, int);
+int tipmic_intr(void *);
+void tipmic_get_lpat(struct tipmic_softc *);
+int32_t tipmic_raw_to_temp(struct tipmic_softc *, int32_t);
+int tipmic_thermal_opreg_handler(void *, int, uint64_t, int, uint64_t *);
+int tipmic_read_pin(void *, int);
+void tipmic_write_pin(void *, int, int);
+
+int
+tipmic_match(struct device *parent, void *match, void *aux)
+{
+ struct i2c_attach_args *ia = aux;
+
+ return (strcmp(ia->ia_name, "INT33F5") == 0);
+}
+
+void
+tipmic_attach(struct device *parent, struct device *self, void *aux)
+{
+ struct tipmic_softc *sc = (struct tipmic_softc *)self;
+ struct i2c_attach_args *ia = aux;
+
+ sc->sc_tag = ia->ia_tag;
+ sc->sc_addr = ia->ia_addr;
+ sc->sc_acpi = acpi_softc;
+ sc->sc_node = ia->ia_cookie;
+
+ if (ia->ia_intr == NULL) {
+ printf(": no interrupt\n");
+ return;
+ }
+
+ /* Mask all interrupts before we install our interrupt handler. */
+ tipmic_write_1(sc, TIPMIC_INTR_MASK, TIPMIC_INTR_MASK_ALL, I2C_F_POLL);
+
+ printf(" %s", iic_intr_string(sc->sc_tag, ia->ia_intr));
+ sc->sc_ih = iic_intr_establish(sc->sc_tag, ia->ia_intr,
+ IPL_BIO, tipmic_intr, sc, sc->sc_dev.dv_xname);
+ if (sc->sc_ih == NULL) {
+ printf(": can't establish interrupt\n");
+ return;
+ }
+
+ printf("\n");
+
+ tipmic_get_lpat(sc);
+ if (sc->sc_lpat == NULL)
+ return;
+
+ sc->sc_gpio.cookie = sc;
+ sc->sc_gpio.read_pin = tipmic_read_pin;
+ sc->sc_gpio.write_pin = tipmic_write_pin;
+ sc->sc_node->gpio = &sc->sc_gpio;
+ acpi_register_gpio(sc->sc_acpi, sc->sc_node);
+
+ /* Register OEM defined address space. */
+ aml_register_regionspace(sc->sc_node, TIPMIC_REGIONSPACE_THERMAL,
+ sc, tipmic_thermal_opreg_handler);
+}
+
+uint8_t
+tipmic_read_1(struct tipmic_softc *sc, uint8_t reg, int flags)
+{
+ uint8_t val;
+ int error;
+
+ iic_acquire_bus(sc->sc_tag, flags);
+ error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
+ &reg, sizeof(reg), &val, sizeof(val), flags);
+ iic_release_bus(sc->sc_tag, flags);
+
+ if (error) {
+ printf("%s: can't read register 0x%02x\n",
+ sc->sc_dev.dv_xname, reg);
+ val = 0xff;
+ }
+
+ return val;
+}
+
+void
+tipmic_write_1(struct tipmic_softc *sc, uint8_t reg, uint8_t val, int flags)
+{
+ int error;
+
+ iic_acquire_bus(sc->sc_tag, flags);
+ error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
+ &reg, sizeof(reg), &val, sizeof(val), flags);
+ iic_release_bus(sc->sc_tag, flags);
+
+ if (error) {
+ printf("%s: can't write register 0x%02x\n",
+ sc->sc_dev.dv_xname, reg);
+ }
+}
+
+int
+tipmic_intr(void *arg)
+{
+ struct tipmic_softc *sc = arg;
+ int handled = 0;
+ uint8_t stat;
+
+ stat = tipmic_read_1(sc, TIPMIC_INTR_STAT, I2C_F_POLL);
+ tipmic_write_1(sc, TIPMIC_INTR_STAT, stat, I2C_F_POLL);
+ if (stat & TIPMIC_INTR_STAT_ADC) {
+ sc->sc_stat_adc = 1;
+ wakeup(&sc->sc_stat_adc);
+ handled = 1;
+ }
+
+ return handled;
+}
+
+void
+tipmic_get_lpat(struct tipmic_softc *sc)
+{
+ struct aml_value res;
+ int i;
+
+ if (aml_evalname(sc->sc_acpi, sc->sc_node, "LPAT", 0, NULL, &res))
+ return;
+ if (res.type != AML_OBJTYPE_PACKAGE)
+ goto out;
+ if (res.length < 4 || (res.length % 2) != 0)
+ goto out;
+
+ sc->sc_lpat_len = res.length / 2;
+ sc->sc_lpat = mallocarray(sc->sc_lpat_len, sizeof(struct acpi_lpat),
+ M_DEVBUF, M_WAITOK);
+
+ for (i = 0; i < sc->sc_lpat_len; i++) {
+ sc->sc_lpat[i].temp = aml_val2int(res.v_package[2 * i]);
+ sc->sc_lpat[i].raw = aml_val2int(res.v_package[2 * i + 1]);
+ }
+
+out:
+ aml_freevalue(&res);
+}
+
+int32_t
+tipmic_raw_to_temp(struct tipmic_softc *sc, int32_t raw)
+{
+ struct acpi_lpat *lpat = sc->sc_lpat;
+ int32_t raw0, delta_raw;
+ int32_t temp0, delta_temp;
+ int i;
+
+ for (i = 1; i < sc->sc_lpat_len; i++) {
+ /* Coefficient can be positive or negative. */
+ if (raw >= lpat[i - 1].raw && raw <= lpat[i].raw)
+ break;
+ if (raw <= lpat[i - 1].raw && raw >= lpat[i].raw)
+ break;
+ }
+ if (i == sc->sc_lpat_len)
+ return -1;
+
+ raw0 = lpat[i - 1].raw;
+ temp0 = lpat[i - 1].temp;
+ delta_raw = lpat[i].raw - raw0;
+ delta_temp = lpat[i].temp - temp0;
+
+ return temp0 + (raw - raw0) * delta_temp / delta_raw;
+}
+
+struct tipmic_regmap {
+ uint8_t address;
+ uint8_t hi, lo;
+};
+
+struct tipmic_regmap tipmic_thermal_regmap[] = {
+ { 0x18, TIPMIC_SYSTEMP_HI, TIPMIC_SYSTEMP_LO }
+};
+
+int
+tipmic_thermal_opreg_handler(void *cookie, int iodir, uint64_t address,
+ int size, uint64_t *value)
+{
+ struct tipmic_softc *sc = cookie;
+ int32_t temp;
+ uint16_t raw;
+ uint8_t hi, lo;
+ uint8_t reg;
+ int i, s;
+
+ /* Only allow 32-bit read access. */
+ if (size != 4 || iodir != ACPI_IOREAD)
+ return -1;
+
+ for (i = 0; i < nitems(tipmic_thermal_regmap); i++) {
+ if (address == tipmic_thermal_regmap[i].address)
+ break;
+ }
+ if (i == nitems(tipmic_thermal_regmap))
+ return -1;
+
+ /* Turn ADC on and select the appropriate channel. */
+ reg = tipmic_read_1(sc, TIPMIC_ADC_CTRL, 0);
+ reg |= TIPMIC_ADC_CTRL_EN;
+ tipmic_write_1(sc, TIPMIC_ADC_CTRL, reg, 0);
+ switch (tipmic_thermal_regmap[i].hi) {
+ case TIPMIC_SYSTEMP_HI:
+ reg |= TIPMIC_ADC_CTRL_CH_SYSTEMP;
+ break;
+ default:
+ panic("%s: unsupported channel", sc->sc_dev.dv_xname);
+ }
+ tipmic_write_1(sc, TIPMIC_ADC_CTRL, reg, 0);
+
+ /* Need to wait 50us before starting the conversion. */
+ delay(50);
+
+ /* Start conversion. */
+ sc->sc_stat_adc = 0;
+ reg |= TIPMIC_ADC_CTRL_START;
+ tipmic_write_1(sc, TIPMIC_ADC_CTRL, reg, 0);
+
+ /*
+ * Block interrupts to prevent I2C access from the interrupt
+ * handler during the completion of the write that unmasks the
+ * ADC interrupt.
+ */
+ s = splbio();
+ reg = tipmic_read_1(sc, TIPMIC_INTR_MASK, I2C_F_POLL);
+ reg &= ~TIPMIC_INTR_MASK_ADC;
+ tipmic_write_1(sc, TIPMIC_INTR_MASK, reg, I2C_F_POLL);
+ splx(s);
+
+ while (sc->sc_stat_adc == 0) {
+ if (tsleep(&sc->sc_stat_adc, PRIBIO, "tipmic", hz)) {
+ printf("%s: ADC timeout\n", sc->sc_dev.dv_xname);
+ break;
+ }
+ }
+
+ /* Mask ADC interrupt again. */
+ s = splbio();
+ reg = tipmic_read_1(sc, TIPMIC_INTR_MASK, I2C_F_POLL);
+ reg |= TIPMIC_INTR_MASK_ADC;
+ tipmic_write_1(sc, TIPMIC_INTR_MASK, reg, I2C_F_POLL);
+ splx(s);
+
+ hi = tipmic_thermal_regmap[i].hi;
+ lo = tipmic_thermal_regmap[i].lo;
+ raw = (tipmic_read_1(sc, hi, 0) & 0x03) << 8;
+ raw |= tipmic_read_1(sc, lo, 0);
+
+ /* Turn ADC off. */
+ reg = tipmic_read_1(sc, TIPMIC_ADC_CTRL, 0);
+ reg &= ~(TIPMIC_ADC_CTRL_EN | TIPMIC_ADC_CTRL_CH_MASK);
+ tipmic_write_1(sc, TIPMIC_ADC_CTRL, reg, 0);
+
+ temp = tipmic_raw_to_temp(sc, raw);
+ if (temp < 0)
+ return -1;
+
+ *value = temp;
+ return 0;
+}
+
+/*
+ * Allegdly the GPIOs are virtual and only there to deal with a
+ * limitation of Microsoft Windows.
+ */
+
+int
+tipmic_read_pin(void *cookie, int pin)
+{
+ return 0;
+}
+
+void
+tipmic_write_pin(void *cookie, int pin, int value)
+{
+}