/* $OpenBSD: axp20x.c,v 1.2 2017/07/24 20:35:26 kettenis Exp $ */
/*
 * Copyright (c) 2005 Mark Kettenis
 *
 * 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/kthread.h>
#include <sys/sensors.h>

#include <dev/i2c/i2cvar.h>

#include <machine/bus.h>
#include <machine/fdt.h>

#include <dev/ofw/openfirm.h>
#include <dev/ofw/fdt.h>

/* Register Name	Address b7 b6 b5 b4 b3 b2 b1 b0	Reset state */
/*
hum_lsb			0xFE	hum_lsb<7:0>			0x00
hum_msb			0xFD	hum_msb<7:0>			0x80
temp_xlsb		0xFC	temp_xlsb<7:4>	0000		0x00
temp_lsb		0xFB	temp_lsb<7:0>			0x00
temp_msb		0xFA	temp_msb<7:0>			0x80
press_xlsb		0xF9	press_xlsb<7:4>	0000		0x00
press_lsb		0xF8	press_lsb<7:0>			0x00
press_msb		0xF7	press_msb<7:0>			0x80
config			0xF5	t_sb[2:0]filter[2:0]spi3w_en[0]	0x00
ctrl_meas		0xF4	osrs_p[2:0]osrs_t[2:0]mode[1:0]	0x00
status			0xF3	measuring[0]im_update[0]	0x00
ctrl_hum		0xF2	osrs_h[2:0]			0x00
calib26..calib41	0xE1-0xF0	calibration data	individual
reset			0xE0	reset[7:0]			0x00
id			0xD0	chip_id[7:0]			0x60
calib00..calib25	0x88-0xA1	calibration data	individual
*/

#define	BME280_CALIB00_23	0x88
#define	BME280_CALIB24		0xa1
#define	BME280_ID		0xd0
#define	BME280_RESET		0xe0
	/* soft reset magic value */
#define	BME280_RESET_MAGIC	0xb6
#define	BME280_CALIB25_31	0xe1
	/* changes become effective only after a write op to ctrl_meas */
#define	BME280_CTRL_HUM		0xf2

#define	BME280_STATUS		0xf3
#define	BME280_STATUS_MASK	0x09
#define	BME280_STATUS_CONV	0x08	/* conversion running */
#define	BME280_STATUS_UPD	0x01	/* update in progress */

#define	BME280_CTRL_MEAS		0xf4
#define	BME280_CTRL_MEAS_MODE_MASK	0x03
#define	BME280_CTRL_MEAS_MODE_SLEEP	0x00
#define	BME280_CTRL_MEAS_MODE_FORCED	0x01
#define	BME280_CTRL_MEAS_MODE_FORCE	0x02
#define	BME280_CTRL_MEAS_MODE_NORMAL	0x03
	/* writes in normal mode may be ignored, not so in sleep mode */
#define	BME280_CONFIG		0xf5
#define	BME280_CONFIG_SPI_EN	0x01

#define	BME280_PRESS_MSB	0xf7
#define	BME280_PRESS_LSB	0xf8
#define	BME280_PRESS_XLSB	0xf9
#define	BME280_TEMP_MSB		0xfa
#define	BME280_TEMP_LSB		0xfb
#define	BME280_TEMP_XLSB	0xfc
#define	BME280_HUM_MSB		0xfd
#define	BME280_HUM_LSB		0xfe

#define	_BME_OVERSAMPLING_SKIP	0x00
#define	_BME_OVERSAMPLING_X1	0x01
#define	_BME_OVERSAMPLING_X2	0x02
#define	_BME_OVERSAMPLING_X4	0x03
#define	_BME_OVERSAMPLING_X8	0x04
#define	_BME_OVERSAMPLING_X16	0x05

#define	_BME_HUMI	0
#define	_BME_TEMP	1
#define	_BME_PRES	2


struct _bme_tt {		/* temperature calibration data */
	int32_t		_t1;
	int32_t		_t2;
	int32_t		_t3;
};

struct _bme_pt {		/* pressure calib. data */
	int64_t		_p1;
	int64_t		_p2;
	int64_t		_p3;
	int64_t		_p4;
	int64_t		_p5;
	int64_t		_p6;
	int64_t		_p7;
	int64_t		_p8;
	int64_t		_p9;
};

struct _bme_ht {		/* humidity calib. data */
	int32_t		_h1;
	int32_t		_h2;
	int32_t		_h3;
	int32_t		_h4;
	int32_t		_h5;
	int32_t		_h6;
};

static inline int	_bme_config_get_filter(uint8_t);
static inline uint8_t	_bme_config_filter(int);
static inline int	_bme_config_get_standby(uint8_t);
static inline uint8_t	_bme_config_standby(int);
static inline int	_bme_get_oversample(uint8_t, uint8_t);
static inline uint8_t	_bme_oversample(uint8_t, uint8_t);

struct bme_softc {
	struct device		sc_dev;
	i2c_tag_t		sc_i2c;
	i2c_addr_t		sc_addr;
	int			sc_node;

	struct ksensor		sc_sensor[3];
	struct ksensordev	sc_sensordev;

		/*
		 * fine resoD:ution temperature value for pressure and
		 * humidity compensation formulas.
		 */
	int32_t			sc_t_fine;
	union {
		struct _bme_tt	sc_temp_trim;
		int32_t		sc_t_trim[3];
	};
	union {
		struct _bme_pt	sc_pres_trim;
		int64_t		sc_p_trim[9];
	};
	union {
		struct _bme_ht	sc_humi_trim;
		int32_t		sc_h_trim[6];
	};
};

int	bme_match(struct device *, void *, void *);
void	bme_attach(struct device *, struct device *, void *);
void	bme_refresh(void *);

int32_t		_bme_comp_temp(struct bme_softc *, int32_t);
uint32_t	_bme_comp_pres(struct bme_softc *, int32_t);
uint32_t	_bme_comp_humi(struct bme_softc *, int32_t);

struct cfattach bme_ca = {
	sizeof(struct bme_softc), bme_match, bme_attach
};

struct cfdriver bme_cd = {
	NULL, "bme", DV_DULL
};

int
bme_match(struct device *parent, void *match, void *aux)
{
	struct i2c_attach_args *ia = aux;
	int node = *(int *)ia->ia_cookie;
	/* XXX made up compatible */
	return OF_is_compatible(node, "bosch,bme280");
}

void
bme_attach(struct device *parent, struct device *self, void *aux)
{
	struct bme_softc *sc = (struct bme_softc *)self;
	struct i2c_attach_args *ia = aux;
	uint8_t reg, data;
        uint8_t _calib[32];
        int i, j;

	sc->sc_i2c = ia->ia_tag;
	sc->sc_addr = ia->ia_addr;
	sc->sc_node = *(int *)ia->ia_cookie;

	iic_acquire_bus(sc->sc_i2c, I2C_F_POLL);

        /* Softreset */
        reg = BME280_RESET;
        data = BME280_RESET_MAGIC;
        if (iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP,
            sc->sc_addr, &reg, 1, &data, 1, I2C_F_POLL)) {
                iic_release_bus(sc->sc_i2c, I2C_F_POLL);
                printf("%s: failed to write reset\n", sc->sc_dev.dv_xname);
                return;
        }

	reg = BME280_ID;
	if (iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP,
	    sc->sc_addr, &reg, 1, &data, 1, I2C_F_POLL)) {
		iic_release_bus(sc->sc_i2c, I2C_F_POLL);
		printf(": cannot read ID register\n");
		return;
	}
	iic_release_bus(sc->sc_i2c, I2C_F_POLL);

        switch (data) {
	case 0x60:
		printf(": BME280 %x\n", data);
		break;
	case 0x58:
	case 0x57:
	case 0x56:
		printf(": sorry, no BMP280 support yet.\n");
		return;
	default:
		printf(": unknown value in ID register %x\n", data);
		return;
	}

       /* write control registers */
        reg = BME280_CTRL_HUM;
        data = _bme_oversample(1, _BME_HUMI);
        if (iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP,
            sc->sc_addr, &reg, 1, &data, 1, I2C_F_POLL)) {
		iic_release_bus(sc->sc_i2c, I2C_F_POLL);
                printf("%s: failed to write control registers: 0x%x, 0x%x.\n",
		    sc->sc_dev.dv_xname, reg, data);
                return;
         }

        reg = BME280_CONFIG;
        data = _bme_config_filter(0) | _bme_config_standby(1000000);
        if (iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP,
            sc->sc_addr, &reg, 1, &data, 1, I2C_F_POLL)) {
		iic_release_bus(sc->sc_i2c, I2C_F_POLL);
                printf("%s: failed to write control registers: 0x%x, 0x%x.\n",
                    sc->sc_dev.dv_xname, reg, data);
		return;
        }

        reg = BME280_CTRL_MEAS;
        data = BME280_CTRL_MEAS_MODE_NORMAL | _bme_oversample(1, _BME_TEMP) |
            _bme_oversample(1, _BME_PRES);
        if (iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP,
            sc->sc_addr, &reg, 1, &data, 1, I2C_F_POLL)) {
                iic_release_bus(sc->sc_i2c, I2C_F_POLL);
		printf("%s: failed to write control registers: 0x%x, 0x%x.\n",
                    sc->sc_dev.dv_xname, reg, data);
                return;
        }

	/* read calibration data */

	delay(4000);
	reg = BME280_CALIB00_23;
	if (iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP,
	    sc->sc_addr, &reg, 1, &_calib[0], 8, I2C_F_POLL)) {
		iic_release_bus(sc->sc_i2c, I2C_F_POLL);
		printf("%s: failed to read calib0 register: 0x%x\n",
	 	    sc->sc_dev.dv_xname, reg);
		return;
	}

	delay(4000);
        reg = BME280_CALIB00_23 + 8;
        if (iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP,
            sc->sc_addr, &reg, 1, &_calib[ + 8], 8, I2C_F_POLL)) {
                iic_release_bus(sc->sc_i2c, I2C_F_POLL);
                printf("%s: failed to read calib0+8 register: 0x%x\n",
                    sc->sc_dev.dv_xname, reg);
                return;
        }

	delay(4000);
        reg = BME280_CALIB00_23 + 16;
        if (iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP,
            sc->sc_addr, &reg, 1, &_calib[ + 16], 8, I2C_F_POLL)) {
                iic_release_bus(sc->sc_i2c, I2C_F_POLL);
                printf("%s: failed to read calib0+16 register: 0x%x\n",
                    sc->sc_dev.dv_xname, reg);
                return;
        }

	delay(4000);
	reg = BME280_CALIB24;
	if (iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP,
	    sc->sc_addr, &reg, 1, &_calib[24], 1, I2C_F_POLL)) {
		iic_release_bus(sc->sc_i2c, I2C_F_POLL);
		printf("%s: failed to read calib24 register: 0x%x\n",
                    sc->sc_dev.dv_xname, reg);
		return;
	}

	reg = BME280_CALIB25_31;
	if (iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP,
	    sc->sc_addr, &reg, 1, &_calib[25], 7, I2C_F_POLL)) {
		iic_release_bus(sc->sc_i2c, I2C_F_POLL);
		printf("%s: failed to read calib25 register: 0x%x\n",
	    	    sc->sc_dev.dv_xname, reg);
		return;
	}

        iic_release_bus(sc->sc_i2c, I2C_F_POLL);

	for (i = 0, j = 0; i < 3; j += 2)
		sc->sc_t_trim[i++] = (_calib[j + 1] << 8) | _calib[j];
	for (i = 0, j = 6; i < 9; j += 2)
		sc->sc_p_trim[i++] = (_calib[j + 1] << 8) | _calib[j];
	sc->sc_h_trim[(i = 0)] = _calib[24];
	sc->sc_h_trim[++i] = (_calib[26] << 8) | _calib[25];
	sc->sc_h_trim[++i] = _calib[27];
	sc->sc_h_trim[++i] = (_calib[28] << 4) | (_calib[29] & 0xf);
	sc->sc_h_trim[++i] = (_calib[30] << 4) | ((_calib[29] & 0xf0) >> 4);
	sc->sc_h_trim[++i] = _calib[31];

	for (i = 1; i < 3; i++)
		if (sc->sc_t_trim[i] & 0x8000)
			sc->sc_t_trim[i] = (-sc->sc_t_trim[i] ^ 0xffff) + 1;
	for (j = 1; j < 9; j++)
		if (sc->sc_p_trim[j] & 0x8000)
			sc->sc_p_trim[j] = (-sc->sc_p_trim[j] ^ 0xffff) + 1;
	for (i = 1; i < 6; i++)		/* XXX last one is signed char? */
		if (sc->sc_h_trim[i] & 0x8000) /* so will never match this */
			sc->sc_h_trim[i] = (-sc->sc_h_trim[i] ^ 0xffff) + 1;

	/* Initialize sensor data. */
	strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname,
	    sizeof(sc->sc_sensordev.xname));
	sc->sc_sensor[_BME_TEMP].type = SENSOR_TEMP;
	sc->sc_sensor[_BME_HUMI].type = SENSOR_HUMIDITY;
	sc->sc_sensor[_BME_PRES].type = SENSOR_PRESSURE;

	if (sensor_task_register(sc, bme_refresh, 1) == NULL) {
		printf("%s: unable to register update task\n",
		    sc->sc_dev.dv_xname);
		return;
	}

	for (i = 0; i < 3; i++)
		sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[i]);
	sensordev_install(&sc->sc_sensordev);
}

int32_t
_bme_comp_temp(struct bme_softc *sc, int32_t raw_t)
{
	int32_t var1, var2, rv_t;
	int32_t _t1 = sc->sc_temp_trim._t1;
	int32_t _t2 = sc->sc_temp_trim._t2;
	int32_t _t3 = sc->sc_temp_trim._t3;

	var1  = ((((raw_t >> 3) - (_t1 << 1))) * _t2) >> 11;
	var2  = (((((raw_t >> 4) - _t1) * ((raw_t >> 4) - _t1)) >> 12) *
	    _t3) >> 14;
	sc->sc_t_fine = var1 + var2;
	rv_t  = (sc->sc_t_fine * 5 + 128) >> 8;
	rv_t = (rv_t + 27315) * 10000;	/* for micro Kelvin */
	return rv_t;
}

uint32_t
_bme_comp_pres(struct bme_softc *sc, int32_t raw_p)
{
	int64_t var1 = (int64_t)sc->sc_t_fine;
	int64_t var2;
	int64_t pa;

	var1 -= 128000;
	var2 = var1 * var1 * sc->sc_pres_trim._p6;
	var2 = var2 + ((var1 * sc->sc_pres_trim._p5) << 17);
	var2 = var2 + (sc->sc_pres_trim._p4 << 35);

	var1 = ((var1 * var1 * sc->sc_pres_trim._p3) >> 8) +
	    ((var1 * sc->sc_pres_trim._p2) << 12);

	var1 = ((((int64_t)1) << 47) + var1) * sc->sc_pres_trim._p1 >> 33;
	if (var1 == 0)
		return 0;

	pa = 1048576 - raw_p;
	pa = (((pa << 31) - var2) * 3125) / var1;
	var1 = (sc->sc_pres_trim._p9 * (pa >> 13) * (pa >> 13)) >> 25;
	var2 = (sc->sc_pres_trim._p8 * pa) >> 19;
	pa = ((pa + var1 + var2) >> 8) + (sc->sc_pres_trim._p7 << 4);
	pa /= 256;
	return (uint32_t)pa;
}

uint32_t
_bme_comp_humi(struct bme_softc *sc, int32_t raw_h)
{
	int32_t rh = (int32_t)sc->sc_t_fine;

	rh -= 76800;
	rh = (((((raw_h << 14) - (sc->sc_humi_trim._h4 << 20) -
	    (sc->sc_humi_trim._h5 * rh)) + 16384) >> 15) *
	    (((((((rh * sc->sc_humi_trim._h6) >> 10) *
	    (((rh * sc->sc_humi_trim._h3) >> 11) + 32768)) >> 10) +
	    2097152) * sc->sc_humi_trim._h2 + 8192) >> 14));
	rh -= (((((rh >> 15) * (rh >> 15)) >> 7) * sc->sc_humi_trim._h1) >> 4);
	if (rh < 0)
		rh = 0;
	if (rh > 419430400)
		rh = 419430400;
	rh >>= 12;
	rh = (rh * 1000) / 1024;
	return (uint32_t)rh;
}

void
bme_refresh(void *arg)
{
	struct bme_softc *sc = arg;
	int32_t raw[3] = { 0, 0, 0 };
	uint8_t reg;
	uint8_t data[8];

	iic_acquire_bus(sc->sc_i2c, 0);
	reg = BME280_PRESS_MSB;
	if (iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP,
	    sc->sc_addr, &reg, 1, &data, sizeof data, 0)) {
		iic_release_bus(sc->sc_i2c, 0);
		sc->sc_sensor[_BME_TEMP].flags |= SENSOR_FINVALID;
		sc->sc_sensor[_BME_PRES].flags |= SENSOR_FINVALID;
		sc->sc_sensor[_BME_HUMI].flags |= SENSOR_FINVALID;
                printf("%s: failed to read data.\n", sc->sc_dev.dv_xname);

		delay(5000);
                /* Softreset */
	        iic_acquire_bus(sc->sc_i2c, I2C_F_POLL);
        	reg = BME280_RESET;
        	data[0] = BME280_RESET_MAGIC;
        	iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP,
            	    sc->sc_addr, &reg, 1, &data, 1, I2C_F_POLL);

		/* write control registers */
        	reg = BME280_CTRL_HUM;
        	data[0] = _bme_oversample(1, _BME_HUMI);
		iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP,
            	    sc->sc_addr, &reg, 1, &data, 1, I2C_F_POLL);
	        reg = BME280_CONFIG;
        	data[0] = _bme_config_filter(0) | _bme_config_standby(1000000);
		iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP,
		    sc->sc_addr, &reg, 1, &data, 1, I2C_F_POLL);
		reg = BME280_CTRL_MEAS;
		data[0] = BME280_CTRL_MEAS_MODE_NORMAL | _bme_oversample(1, _BME_TEMP) |
	    	    _bme_oversample(1, _BME_PRES);
		iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP,
		    sc->sc_addr, &reg, 1, &data, 1, I2C_F_POLL);
                iic_release_bus(sc->sc_i2c, I2C_F_POLL);

                return;
        }

	iic_release_bus(sc->sc_i2c, 0);

	/* got raw */
	raw[_BME_HUMI] = (data[6] << 8) | data[7];
	raw[_BME_TEMP] = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4);
	raw[_BME_PRES] = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4);

	/* do compensate raw values */
	sc->sc_sensor[_BME_TEMP].value = _bme_comp_temp(sc, raw[_BME_TEMP]);
	sc->sc_sensor[_BME_TEMP].flags &= ~SENSOR_FINVALID;
	sc->sc_sensor[_BME_PRES].value = _bme_comp_pres(sc, raw[_BME_PRES]);
	sc->sc_sensor[_BME_PRES].flags &= ~SENSOR_FINVALID;
	sc->sc_sensor[_BME_HUMI].value = _bme_comp_humi(sc, raw[_BME_HUMI]);
	sc->sc_sensor[_BME_HUMI].flags &= ~SENSOR_FINVALID;
}

int
_bme_config_get_filter(uint8_t x)
{
	switch ((x >> 2) & 7) {
	case 0x00:
		return 0x00;
	case 0x01:
		return 2;
	case 0x02:
		return 4;
	case 0x03:
		return 8;
	default:
	case 0x04:
		return 16;
	}
}

uint8_t
_bme_config_filter(int x)
{
	switch (x) {
	default:
	case 0:
		return 0x00;
	case 2:
		return 0x01;
	case 4:
		return 0x02;
	case 8:
		return 0x03;
	case 16:
		return 0x04;
	}
}

int
_bme_config_get_standby(uint8_t x)
{
	switch ((x >> 5) & 7) {
	case 0x00:
		return 500;
	case 0x06:
		return 10000;
	case 0x07:
		return 20000;
	case 0x01:
		return 62500;
	case 0x02:
		return 125000;
	case 0x03:
		return 250000;
	case 0x04:
		return 500000;
	default:
	case 0x05:
		return 1000000;
	}
}

uint8_t
_bme_config_standby(int x)
{
	switch (x) {
	case 500:
		return 0x00U << 5;
	case 10000:
		return 0x06U << 5;
	case 20000:
		return 0x07U << 5;
	case 62500:
		return 0x01U << 5;
	case 125000:
		return 0x02U << 5;
	case 250000:
		return 0x03U << 5;
	case 500000:
		return 0x04U << 5;
	default:
	case 1000000:
		return 0x05U << 5;
	}
}


int
_bme_get_oversample(uint8_t x, uint8_t s)
{
	if (s == _BME_TEMP)
		x >>= 2;
	else if (s == _BME_PRES)
		x >>= 5;
	else if (s != _BME_HUMI)
		return -1;

	switch (x & 7) {
	case _BME_OVERSAMPLING_SKIP:
		return 0;
	case _BME_OVERSAMPLING_X1:
		return 1;
	case _BME_OVERSAMPLING_X2:
		return 2;
	case _BME_OVERSAMPLING_X4:
		return 4;
	case _BME_OVERSAMPLING_X16:
	default:
		return 16;
	}
}

uint8_t
_bme_oversample(uint8_t x, uint8_t s)
{

	if (s == _BME_TEMP)
		s = 2;
	else if (s == _BME_PRES)
		s = 5;
	else if (s != _BME_HUMI)
		return 0;

	switch (x) {
	default:
	case 0:
		return 0;
	case 1:
		return _BME_OVERSAMPLING_X1 << s;
	case 2:
		return _BME_OVERSAMPLING_X2 << s;
	case 4:
		return _BME_OVERSAMPLING_X4 << s;
	case 16:
		return _BME_OVERSAMPLING_X16 << s;
	}
}
