Edit

IABSD.fr/src/sys/dev/acpi/tipmic.c

Branch :

  • Show log

    Commit

  • Author : dlg
    Date : 2023-03-04 01:23:40
    Hash : c6789160
    Message : handle polling when cold in tipmic_thermal_opreg_handler(). this allows me to boot if acpitz is using tipmic instead of getting stuck. tipmic would spin on tsleep, which returns immediately with 0 when cold, waiting for a value to be set by the tipmic interrupt handler. cos the box is cold the interrupt is masked, so the tsleep loop never ended. patrick@ helped me find this ok kettenis@

  • sys/dev/acpi/tipmic.c
  • /*	$OpenBSD: tipmic.c,v 1.8 2023/03/04 01:23:40 dlg 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_LDO1_CTRL		0x41
    #define TIPMIC_LDO2_CTRL		0x42
    #define TIPMIC_LDO3_CTRL		0x43
    #define TIPMIC_LDO5_CTRL		0x45
    #define TIPMIC_LDO6_CTRL		0x46
    #define TIPMIC_LDO7_CTRL		0x47
    #define TIPMIC_LDO8_CTRL		0x48
    #define TIPMIC_LDO9_CTRL		0x49
    #define TIPMIC_LDO10_CTRL		0x4a
    #define TIPMIC_LDO11_CTRL		0x4b
    #define TIPMIC_LDO12_CTRL		0x4c
    #define TIPMIC_LDO13_CTRL		0x4d
    #define TIPMIC_LDO14_CTRL		0x4e
    #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
    #define TIPMIC_REGIONSPACE_POWER	0x8d
    
    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 *);
    
    const 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_power_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);
    	aml_register_regionspace(sc->sc_node, TIPMIC_REGIONSPACE_POWER,
    	    sc, tipmic_power_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[] = {
    	{ 0x00, TIPMIC_SYSTEMP_HI, TIPMIC_SYSTEMP_LO },
    	{ 0x18, TIPMIC_SYSTEMP_HI, TIPMIC_SYSTEMP_LO }
    };
    
    static int
    tipmic_wait_adc(struct tipmic_softc *sc)
    {
    	int i;
    
    	if (!cold) {
    		return (tsleep_nsec(&sc->sc_stat_adc, PRIBIO, "tipmic",
    		    SEC_TO_NSEC(1)));
    	}
    
    	for (i = 0; i < 1000; i++) {
    		delay(1000);
    		if (tipmic_intr(sc) == 1)
    			return (0);
    	}
    
    	return (EWOULDBLOCK);
    }
    
    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)) {
    		printf("%s: addr 0x%02llx\n", __func__, address);
    		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 (tipmic_wait_adc(sc)) {
    			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;
    }
    
    struct tipmic_regmap tipmic_power_regmap[] = {
    	{ 0x00, TIPMIC_LDO1_CTRL },
    	{ 0x04, TIPMIC_LDO2_CTRL },
    	{ 0x08, TIPMIC_LDO3_CTRL },
    	{ 0x0c, TIPMIC_LDO5_CTRL },
    	{ 0x10, TIPMIC_LDO6_CTRL },
    	{ 0x14, TIPMIC_LDO7_CTRL },
    	{ 0x18, TIPMIC_LDO8_CTRL },
    	{ 0x1c, TIPMIC_LDO9_CTRL },
    	{ 0x20, TIPMIC_LDO10_CTRL },
    	{ 0x24, TIPMIC_LDO11_CTRL },
    	{ 0x28, TIPMIC_LDO12_CTRL },
    	{ 0x2c, TIPMIC_LDO13_CTRL },
    	{ 0x30, TIPMIC_LDO14_CTRL }
    };
    
    int
    tipmic_power_opreg_handler(void *cookie, int iodir, uint64_t address,
        int size, uint64_t *value)
    {
    	struct tipmic_softc *sc = cookie;
    	uint8_t reg, val;
    	int i;
    
    	/* Only allow 32-bit access. */
    	if (size != 4)
    		return -1;
    
    	for (i = 0; i < nitems(tipmic_power_regmap); i++) {
    		if (address == tipmic_power_regmap[i].address)
    			break;
    	}
    	if (i == nitems(tipmic_power_regmap)) {
    		printf("%s: addr 0x%02llx\n", __func__, address);
    		return -1;
    	}
    
    	reg = tipmic_power_regmap[i].hi;
    	val = tipmic_read_1(sc, reg, 0);
    	if (iodir == ACPI_IOREAD) {
    		*value = val & 0x1;
    	} else {
    		if (*value)
    			val |= 0x1;
    		else
    			val &= ~0x1;
    		tipmic_write_1(sc, reg, val, 0);
    	}
    
    	return 0;
    }
    
    /* 
     * Allegedly 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)
    {
    }