root/sys/dev/spi/bmx280thpspi.c
/*      $NetBSD: bmx280thpspi.c,v 1.7 2025/09/13 16:16:40 thorpej Exp $ */

/*
 * Copyright (c) 2022 Brad Spencer <brad@anduin.eldar.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/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: bmx280thpspi.c,v 1.7 2025/09/13 16:16:40 thorpej Exp $");

/*
 * SPI driver for the Bosch BMP280 / BME280 sensor.
 * Uses the common bmx280thp driver to do the real work.
*/

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/device.h>
#include <sys/module.h>
#include <sys/conf.h>
#include <sys/sysctl.h>
#include <sys/mutex.h>
#include <sys/condvar.h>
#include <sys/pool.h>
#include <sys/kmem.h>

#include <dev/sysmon/sysmonvar.h>
#include <dev/i2c/i2cvar.h>
#include <dev/spi/spivar.h>
#include <dev/ic/bmx280reg.h>
#include <dev/ic/bmx280var.h>

struct bmx280_spi_softc {
        struct bmx280_sc        sc_bmx280;
        spi_handle_t            sc_sh;
};

#define BMX280_TO_SPI(sc)       \
        container_of((sc), struct bmx280_spi_softc, sc_bmx280)

static int      bmx280thpspi_match(device_t, cfdata_t, void *);
static void     bmx280thpspi_attach(device_t, device_t, void *);
static int      bmx280thpspi_detach(device_t, int);

CFATTACH_DECL_NEW(bmx280thpspi, sizeof(struct bmx280_spi_softc),
    bmx280thpspi_match, bmx280thpspi_attach, bmx280thpspi_detach, NULL);

/* The SPI interface of the chip, assuming that it has managed to get into that
 * mode to start with, is pretty simple.  Simply send the register MINUS the 7th
 * bit which will be 1 and then do as many reads as you want.  The chip will
 * auto increment for you.
 *
 * The delays are only hinted at in the data sheet.
 */

static int
bmx280thpspi_read_reg_direct(spi_handle_t sh, uint8_t reg, uint8_t *buf,
    size_t rlen)
{
        int err = 0;
        uint8_t rreg = reg | 0x80;

        if (buf != NULL) {
                err = spi_send_recv(sh, 1, &rreg,
                    rlen, buf);
        } else {
                err = spi_send(sh, 1, &rreg);
        }

        return err;
}

static int
bmx280thpspi_read_reg(struct bmx280_sc *sc, uint8_t reg, uint8_t *buf,
    size_t rlen)
{
        struct bmx280_spi_softc *ssc = BMX280_TO_SPI(sc);

        return bmx280thpspi_read_reg_direct(ssc->sc_sh, reg, buf, rlen);
}

/* SPI writes to this device are normal enough.  You send the register
 * you want making sure that the high bit, 0x80, is clear and then the
 * data.  These pairs can be repeated as many times as you like.
 */
static int
bmx280thpspi_write_reg_direct(spi_handle_t sh, uint8_t *buf, size_t slen)
{
        int err = 0;
        int i;

        /* XXX -
           this is probably  BAD thing to do... but we must insure that the
           registers have a cleared bit.. otherwise it is a read ....
        */

        for(i = 0; i < slen;i+=2) {
                buf[i] = buf[i] & 0x7F;
        }

        err = spi_send(sh, slen, buf);

        return err;
}

static int
bmx280thpspi_write_reg(struct bmx280_sc *sc, uint8_t *buf, size_t slen)
{
        struct bmx280_spi_softc *ssc = BMX280_TO_SPI(sc);

        return bmx280thpspi_write_reg_direct(ssc->sc_sh, buf, slen);
}

/* These are to satisfy the common code */
static int
bmx280thpspi_acquire_bus(struct bmx280_sc *sc __unused)
{
        return 0;
}

static void
bmx280thpspi_release_bus(struct bmx280_sc *sc __unused)
{
        return;
}

static const struct bmx280_accessfuncs bmx280_spi_accessfuncs = {
        .acquire_bus    =       bmx280thpspi_acquire_bus,
        .release_bus    =       bmx280thpspi_release_bus,
        .read_reg       =       bmx280thpspi_read_reg,
        .write_reg      =       bmx280thpspi_write_reg,
};

/* Nothing more is done here.  Assumptions on whether or not
 * the SPI interface is set up may not be proper.... for better
 * or worse... and there is setting that are needed such as the
 * SPI mode and bus speed that really should not be done here, so
 * any active match might not work anyway.
 */
static int
bmx280thpspi_match(device_t parent, cfdata_t match, void *aux)
{
        struct spi_attach_args *sa = aux;
        int match_result;

        if (spi_use_direct_match(sa, bmx280_compat_data, &match_result)) {
                return match_result;
        }

        return SPI_MATCH_DEFAULT;
}

static void
bmx280thpspi_attach(device_t parent, device_t self, void *aux)
{
        struct bmx280_spi_softc *ssc = device_private(self);
        struct bmx280_sc *sc = &ssc->sc_bmx280;
        struct spi_attach_args *sa = aux;
        int error;

        sc->sc_dev = self;
        sc->sc_funcs = &bmx280_spi_accessfuncs;

        ssc->sc_sh = sa->sa_handle;

        /* Configure for 1MHz and SPI mode 0 according to the data sheet.
         * The chip will actually handle a number of different modes and
         * can go a lot faster, just use this for now...
         */
        error = spi_configure(self, sa->sa_handle, SPI_MODE_0, SPI_FREQ_MHz(1));
        if (error) {
                return;
        }

        /* Please note that if the pins are not set up for SPI, the attachment
         * will probably not work out.
         */
        bmx280_attach(sc);
}

static int
bmx280thpspi_detach(device_t self, int flags)
{
        struct bmx280_spi_softc *ssc = device_private(self);

        return bmx280_detach(&ssc->sc_bmx280, flags);
}