summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Kettenis <kettenis@cvs.openbsd.org>2018-05-21 13:37:32 +0000
committerMark Kettenis <kettenis@cvs.openbsd.org>2018-05-21 13:37:32 +0000
commit172de1b5d4bde86a6138b8f0ae3c66ad2c0c5046 (patch)
treee2f2b64926f9063972dff44a159e842e078580eb
parent5429fe01ea82724e0f2074519e4a3c0e7754695c (diff)
Add ccpmic(4), a driver for the "Intel" Crystal Cove PMIC. This is actually
a ROHM part (BD2613GW) and once more the datasheet is only available under NDA. Disabled for now until bugs in dwiic(4) are fixed.
-rw-r--r--sys/dev/acpi/ccpmic.c311
-rw-r--r--sys/dev/acpi/files.acpi7
2 files changed, 317 insertions, 1 deletions
diff --git a/sys/dev/acpi/ccpmic.c b/sys/dev/acpi/ccpmic.c
new file mode 100644
index 00000000000..efcc749ffa7
--- /dev/null
+++ b/sys/dev/acpi/ccpmic.c
@@ -0,0 +1,311 @@
+/* $OpenBSD: ccpmic.c,v 1.1 2018/05/21 13:37:31 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 CCPMIC_V1P8SX 0x5d
+#define CCPMIC_V1P2SX 0x61
+#define CCPMIC_V2P85SX 0x66
+#define CCPMIC_PWR_ON (1 << 0)
+#define CCPMIC_PWR_SEL (1 << 1)
+#define CCPMIC_SYS2_THRM_RSLT_H 0x78
+#define CCPMIC_SYS2_THRM_RSLT_L 0x79
+
+#define CCPMIC_REGIONSPACE_THERMAL 0x8c
+#define CCPMIC_REGIONSPACE_POWER 0x8d
+
+struct acpi_lpat {
+ int32_t temp;
+ int32_t raw;
+};
+
+struct ccpmic_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;
+
+ struct acpi_lpat *sc_lpat;
+ size_t sc_lpat_len;
+
+ struct acpi_gpio sc_gpio;
+};
+
+int ccpmic_match(struct device *, void *, void *);
+void ccpmic_attach(struct device *, struct device *, void *);
+
+struct cfattach ccpmic_ca = {
+ sizeof(struct ccpmic_softc), ccpmic_match, ccpmic_attach
+};
+
+struct cfdriver ccpmic_cd = {
+ NULL, "ccpmic", DV_DULL
+};
+
+uint8_t ccpmic_read_1(struct ccpmic_softc *, uint8_t, int);
+void ccpmic_write_1(struct ccpmic_softc *, uint8_t, uint8_t, int);
+void ccpmic_get_lpat(struct ccpmic_softc *);
+int32_t ccpmic_raw_to_temp(struct ccpmic_softc *, int32_t);
+int ccpmic_thermal_opreg_handler(void *, int, uint64_t, int, uint64_t *);
+int ccpmic_power_opreg_handler(void *, int, uint64_t, int, uint64_t *);
+int ccpmic_read_pin(void *, int);
+void ccpmic_write_pin(void *, int, int);
+
+int
+ccpmic_match(struct device *parent, void *match, void *aux)
+{
+ struct i2c_attach_args *ia = aux;
+
+ return (strcmp(ia->ia_name, "INT33FD") == 0);
+}
+
+void
+ccpmic_attach(struct device *parent, struct device *self, void *aux)
+{
+ struct ccpmic_softc *sc = (struct ccpmic_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;
+
+ printf("\n");
+
+ ccpmic_get_lpat(sc);
+ if (sc->sc_lpat == NULL)
+ return;
+
+ sc->sc_gpio.cookie = sc;
+ sc->sc_gpio.read_pin = ccpmic_read_pin;
+ sc->sc_gpio.write_pin = ccpmic_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, CCPMIC_REGIONSPACE_THERMAL,
+ sc, ccpmic_thermal_opreg_handler);
+ aml_register_regionspace(sc->sc_node, CCPMIC_REGIONSPACE_POWER,
+ sc, ccpmic_power_opreg_handler);
+}
+
+uint8_t
+ccpmic_read_1(struct ccpmic_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
+ccpmic_write_1(struct ccpmic_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);
+ }
+}
+
+void
+ccpmic_get_lpat(struct ccpmic_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
+ccpmic_raw_to_temp(struct ccpmic_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 ccpmic_regmap {
+ uint8_t address;
+ uint8_t hi, lo;
+};
+
+struct ccpmic_regmap ccpmic_thermal_regmap[] = {
+ { 0x18, CCPMIC_SYS2_THRM_RSLT_H, CCPMIC_SYS2_THRM_RSLT_L }, /* TMP2 */
+};
+
+int
+ccpmic_thermal_opreg_handler(void *cookie, int iodir, uint64_t address,
+ int size, uint64_t *value)
+{
+ struct ccpmic_softc *sc = cookie;
+ int32_t temp;
+ uint16_t raw;
+ uint8_t lo, hi;
+ int i;
+
+ /* Only allow 32-bit read access. */
+ if (size != 4 || iodir != ACPI_IOREAD)
+ return -1;
+
+ for (i = 0; i < nitems(ccpmic_thermal_regmap); i++) {
+ if (address == ccpmic_thermal_regmap[i].address)
+ break;
+ }
+ if (i == nitems(ccpmic_thermal_regmap)) {
+ printf("%s: addr 0x%02llx\n", __func__, address);
+ return -1;
+ }
+
+ lo = ccpmic_thermal_regmap[i].lo;
+ hi = ccpmic_thermal_regmap[i].hi;
+ raw = ccpmic_read_1(sc, lo, 0);
+ raw |= (ccpmic_read_1(sc, hi, 0) & 0x03) << 8;
+
+ temp = ccpmic_raw_to_temp(sc, raw);
+ if (temp < 0)
+ return -1;
+
+ *value = temp;
+ return 0;
+}
+
+struct ccpmic_regmap ccpmic_power_regmap[] = {
+ { 0x24, CCPMIC_V2P85SX }, /* X285 */
+ { 0x48, CCPMIC_V1P8SX }, /* V18X */
+ { 0x50, CCPMIC_V1P2SX }, /* V12X */
+};
+
+int
+ccpmic_power_opreg_handler(void *cookie, int iodir, uint64_t address,
+ int size, uint64_t *value)
+{
+ struct ccpmic_softc *sc = cookie;
+ uint8_t reg, val;
+ int i;
+
+ /* Only allow 32-bit access. */
+ if (size != 4)
+ return -1;
+
+ for (i = 0; i < nitems(ccpmic_power_regmap); i++) {
+ if (address == ccpmic_power_regmap[i].address)
+ break;
+ }
+ if (i == nitems(ccpmic_power_regmap)) {
+ printf("%s: addr 0x%02llx\n", __func__, address);
+ return -1;
+ }
+
+ reg = ccpmic_power_regmap[i].hi;
+ val = ccpmic_read_1(sc, reg, 0);
+ if (iodir == ACPI_IOREAD) {
+ if ((val & CCPMIC_PWR_SEL) && (val & CCPMIC_PWR_ON))
+ *value = 1;
+ else
+ *value = 0;
+ } else {
+ if (*value)
+ val |= CCPMIC_PWR_ON;
+ else
+ val &= ~CCPMIC_PWR_ON;
+ ccpmic_write_1(sc, reg, val | CCPMIC_PWR_SEL, 0);
+ }
+
+ return 0;
+}
+
+/*
+ * Allegdly the GPIOs are virtual and only there to deal with a
+ * limitation of Microsoft Windows.
+ */
+
+int
+ccpmic_read_pin(void *cookie, int pin)
+{
+ return 0;
+}
+
+void
+ccpmic_write_pin(void *cookie, int pin, int value)
+{
+}
diff --git a/sys/dev/acpi/files.acpi b/sys/dev/acpi/files.acpi
index 31afd975601..e0163a382d7 100644
--- a/sys/dev/acpi/files.acpi
+++ b/sys/dev/acpi/files.acpi
@@ -1,4 +1,4 @@
-# $OpenBSD: files.acpi,v 1.41 2018/05/20 19:30:21 kettenis Exp $
+# $OpenBSD: files.acpi,v 1.42 2018/05/21 13:37:31 kettenis Exp $
#
# Config file and device description for machine-independent ACPI code.
# Included by ports that need it.
@@ -132,6 +132,11 @@ device tipmic
attach tipmic at i2c
file dev/acpi/tipmic.c tipmic
+# "Intel" Crystal Cove PMIC
+device ccpmic
+attach ccpmic at i2c
+file dev/acpi/ccpmic.c ccpmic
+
# SD Host Controller
attach sdhc at acpi with sdhc_acpi
file dev/acpi/sdhc_acpi.c sdhc_acpi