Current-Users archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Driver for the ST LIS302DL MEMS motion sensor



                        Hi current-users,

I am writing a driver for the ST LIS302DL MEMS motion sensor. This
component can be found on the I2C bus in the Nokia N900 smartphone
hardware, which is where it currently expects to attach:
# Accelerometer
lis302dl0       at iic2 addr 0x1d

It seems to be otherwise available in a number of other hardware
devices, although not always on I2C apparently. The Openmoko Freerunner
features one on the SPI interface for instance:
http://wiki.openmoko.org/wiki/ST_LIS302DL

I am not currently unable to test it besides just attaching, so I will
welcome comments before committing it, and of course suggestions as to
how to make it more portable (eg usable on other buses as well).

HTH,
-- 
khorben
/* $NetBSD$ */

/*
 * Copyright (c) 2013 Pierre Pronchery <khorben%defora.org@localhost>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * ST LIS302DL MEMS motion sensor
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD$");

#include <sys/param.h>
#include <sys/device.h>

#include <dev/i2c/i2cvar.h>
#include <dev/sysmon/sysmonvar.h>

/* registers */
#define LIS302DL_REG_WHO_AM_I           0x0f
#define LIS302DL_REG_CTRL1              0x20
#define LIS302DL_REG_CTRL2              0x21
#define LIS302DL_REG_CTRL3              0x22
#define LIS302DL_REG_FILTER_RESET       0x23
#define LIS302DL_REG_STATUS             0x27
#define LIS302DL_REG_OUTX               0x29
#define LIS302DL_REG_OUTY               0x2b
#define LIS302DL_REG_OUTZ               0x2d
#define LIS302DL_REG_FF_WU_CFG1         0x30
#define LIS302DL_REG_FF_WU_SRC1         0x31
#define LIS302DL_REG_FF_WU_THS1         0x32
#define LIS302DL_REG_FF_WU_DURATION_1   0x33
#define LIS302DL_REG_FF_WU_CFG2         0x34
#define LIS302DL_REG_FF_WU_SRC2         0x35
#define LIS302DL_REG_FF_WU_THS2         0x36
#define LIS302DL_REG_CLICK_CFG          0x38
#define LIS302DL_REG_CLICK_SRC          0x39
#define LIS302DL_REG_CLICK_THSY_X       0x3b
#define LIS302DL_REG_CLICK_THSZ         0x3c

/* constants */
#define LIS302DL_ID                     0x3b

#define LIS302DL_CTRL1_XEN              __BIT(0)
#define LIS302DL_CTRL1_YEN              __BIT(1)
#define LIS302DL_CTRL1_ZEN              __BIT(2)
#define LIS302DL_CTRL1_STM              __BIT(3)
#define LIS302DL_CTRL1_STP              __BIT(4)
#define LIS302DL_CTRL1_FS               __BIT(5)
#define LIS302DL_CTRL1_PD               __BIT(6)
#define LIS302DL_CTRL1_DR               __BIT(7)

#define LIS302DL_CTRL2_HP_COEFF1        __BIT(0)
#define LIS302DL_CTRL2_HP_COEFF2        __BIT(1)
#define LIS302DL_CTRL2_HP_FF_WU1        __BIT(2)
#define LIS302DL_CTRL2_HP_FF_WU2        __BIT(3)
#define LIS302DL_CTRL2_FDS              __BIT(4)
#define LIS302DL_CTRL2_BOOT             __BIT(6)
#define LIS302DL_CTRL2_SIM              __BIT(7)

#define LIS302DL_STATUS_XDA             __BIT(0)
#define LIS302DL_STATUS_YDA             __BIT(1)
#define LIS302DL_STATUS_ZDA             __BIT(2)
#define LIS302DL_STATUS_ZYXDA           __BIT(3)
#define LIS302DL_STATUS_XOR             __BIT(4)
#define LIS302DL_STATUS_YOR             __BIT(5)
#define LIS302DL_STATUS_ZOR             __BIT(6)
#define LIS302DL_STATUS_ZXYOR           __BIT(7)

#define LIS302DL_FF_WU_CFG1_XLIE        __BIT(0)
#define LIS302DL_FF_WU_CFG1_XHIE        __BIT(1)
#define LIS302DL_FF_WU_CFG1_YLIE        __BIT(2)
#define LIS302DL_FF_WU_CFG1_YHIE        __BIT(3)
#define LIS302DL_FF_WU_CFG1_ZLIE        __BIT(4)
#define LIS302DL_FF_WU_CFG1_ZHIE        __BIT(5)
#define LIS302DL_FF_WU_CFG1_LIR         __BIT(6)
#define LIS302DL_FF_WU_CFG1_AOI         __BIT(7)

#define LIS302DL_SRC1_XL                __BIT(0)
#define LIS302DL_SRC1_XE                __BIT(1)
#define LIS302DL_SRC1_YL                __BIT(2)
#define LIS302DL_SRC1_YH                __BIT(3)
#define LIS302DL_SRC1_ZL                __BIT(4)
#define LIS302DL_SRC1_ZH                __BIT(5)
#define LIS302DL_SRC1_IA                __BIT(6)

#define LIS302DL_FF_WU_CFG2_XLIE        __BIT(0)
#define LIS302DL_FF_WU_CFG2_XHIE        __BIT(1)
#define LIS302DL_FF_WU_CFG2_YLIE        __BIT(2)
#define LIS302DL_FF_WU_CFG2_YHIE        __BIT(3)
#define LIS302DL_FF_WU_CFG2_ZLIE        __BIT(4)
#define LIS302DL_FF_WU_CFG2_ZHIE        __BIT(5)
#define LIS302DL_FF_WU_CFG2_LIR         __BIT(6)
#define LIS302DL_FF_WU_CFG2_AOI         __BIT(7)

#define LIS302DL_CLICK_CFG_X            __BIT(0)
#define LIS302DL_CLICK_CFG_X2           __BIT(1)
#define LIS302DL_CLICK_CFG_Y            __BIT(2)
#define LIS302DL_CLICK_CFG_Y2           __BIT(3)
#define LIS302DL_CLICK_CFG_Z            __BIT(4)
#define LIS302DL_CLICK_CFG_Z2           __BIT(5)
#define LIS302DL_CLICK_CFG_LIR          __BIT(6)

enum lis302dl_sensors {
        LIS302DL_SENSOR_XACCEL = 0,
        LIS302DL_SENSOR_YACCEL,
        LIS302DL_SENSOR_ZACCEL
};
#define LIS302DL_SENSOR_LAST            LIS302DL_SENSOR_ZACCEL
#define LIS302DL_SENSOR_CNT             (LIS302DL_SENSOR_LAST + 1)

/* variables */
struct lis302dl_softc {
        device_t                sc_dev;
        i2c_tag_t               sc_i2c;
        i2c_addr_t              sc_addr;

        struct sysmon_envsys    *sc_sme;
        envsys_data_t           sc_sensor[LIS302DL_SENSOR_CNT];

        void *                  sc_intr;
};

static int      lis302dl_match(device_t, cfdata_t, void *);
static void     lis302dl_attach(device_t, device_t, void *);
static int      lis302dl_detach(device_t, int);

static int      lis302dl_reset(struct lis302dl_softc *);

static int      lis302dl_intr(void *);

static int      lis302dl_read_1(struct lis302dl_softc *, uint8_t, uint8_t *);
static int      lis302dl_write_1(struct lis302dl_softc *, uint8_t, uint8_t);

CFATTACH_DECL_NEW(lis302dl, sizeof(struct lis302dl_softc),
        lis302dl_match, lis302dl_attach, lis302dl_detach, NULL);

static int
lis302dl_match(device_t parent, cfdata_t match, void *aux)
{
        return 1;
}

static void
lis302dl_attach(device_t parent, device_t self, void *aux)
{
        struct lis302dl_softc *sc = device_private(self);
        struct i2c_attach_args *ia = aux;
        uint8_t u8;
        int i;

        sc->sc_dev = self;
        sc->sc_i2c = ia->ia_tag;
        sc->sc_addr = ia->ia_addr;

        aprint_naive("\n");
        aprint_normal(": motion sensor\n");

        lis302dl_reset(sc);

        sc->sc_intr = iic_smbus_intr_establish(sc->sc_i2c, lis302dl_intr, sc);
        if (sc->sc_intr == NULL) {
                aprint_error("Could not establish interrupt\n");
                return;
        }

        iic_acquire_bus(sc->sc_i2c, 0);

        /* enable free-fall wake up */
        u8 = LIS302DL_FF_WU_CFG1_XLIE | LIS302DL_FF_WU_CFG1_XHIE
                | LIS302DL_FF_WU_CFG1_YLIE | LIS302DL_FF_WU_CFG1_YHIE
                | LIS302DL_FF_WU_CFG1_ZLIE | LIS302DL_FF_WU_CFG1_ZHIE;
        lis302dl_write_1(sc, LIS302DL_REG_FF_WU_CFG1, u8);
        u8 = LIS302DL_FF_WU_CFG2_XLIE | LIS302DL_FF_WU_CFG2_XHIE
                | LIS302DL_FF_WU_CFG2_YLIE | LIS302DL_FF_WU_CFG2_YHIE
                | LIS302DL_FF_WU_CFG2_ZLIE | LIS302DL_FF_WU_CFG2_ZHIE;
        lis302dl_write_1(sc, LIS302DL_REG_FF_WU_CFG2, u8);

        /* enable clicks */
        u8 = LIS302DL_CLICK_CFG_X | LIS302DL_CLICK_CFG_X2
                | LIS302DL_CLICK_CFG_Y | LIS302DL_CLICK_CFG_Y2
                | LIS302DL_CLICK_CFG_Z | LIS302DL_CLICK_CFG_Z2;
        lis302dl_write_1(sc, LIS302DL_REG_CLICK_CFG, u8);

        iic_release_bus(sc->sc_i2c, 0);

        /* initialize the sensors */
#define INITDATA(idx, unit, string)                                     \
        sc->sc_sensor[idx].units = unit;                                \
        strlcpy(sc->sc_sensor[idx].desc, string,                        \
                        sizeof(sc->sc_sensor[idx].desc));
        INITDATA(LIS302DL_SENSOR_XACCEL, ENVSYS_INTEGER, "x-acceleration");
        INITDATA(LIS302DL_SENSOR_YACCEL, ENVSYS_INTEGER, "y-acceleration");
        INITDATA(LIS302DL_SENSOR_ZACCEL, ENVSYS_INTEGER, "z-acceleration");

        sc->sc_sme = sysmon_envsys_create();
        for (i = 0; i < LIS302DL_SENSOR_CNT; i++) {
                sc->sc_sensor[i].state = ENVSYS_SVALID;

                if (sc->sc_sensor[i].units == ENVSYS_INTEGER) {
                        sc->sc_sensor[i].flags = ENVSYS_FHAS_ENTROPY;
                }

                if (sysmon_envsys_sensor_attach(sc->sc_sme,
                                        &sc->sc_sensor[i])) {
                        sysmon_envsys_destroy(sc->sc_sme);
                        break;
                }
        }

        /* register with sysmon_envsys(9) */
        sc->sc_sme->sme_name = device_xname(self);
        sc->sc_sme->sme_flags = SME_DISABLE_REFRESH;

        if ((i = sysmon_envsys_register(sc->sc_sme))) {
                aprint_error_dev(self,
                                "unable to register with sysmon (%d)\n", i);
                sysmon_envsys_destroy(sc->sc_sme);
                return;
        }
}

static int
lis302dl_detach(device_t self, int flags)
{
        struct lis302dl_softc *sc = device_private(self);

        if (sc->sc_sme)
                sysmon_envsys_unregister(sc->sc_sme);

        /* FIXME should probably also disable interrupts */

        return 0;
}

static int
lis302dl_reset(struct lis302dl_softc *sc)
{
        uint8_t u8;

        iic_acquire_bus(sc->sc_i2c, 0);

        /* reboot the device */
        if (lis302dl_read_1(sc, LIS302DL_REG_CTRL2, &u8) == 0) {
                u8 |= LIS302DL_CTRL2_BOOT;
                lis302dl_write_1(sc, LIS302DL_REG_CTRL2, u8);
        }

        /* reset the internal filter */
        lis302dl_read_1(sc, LIS302DL_REG_FILTER_RESET, &u8);

        /* enable all three axis and power */
        u8 = LIS302DL_CTRL1_XEN | LIS302DL_CTRL1_YEN | LIS302DL_CTRL1_ZEN
                | LIS302DL_CTRL1_PD;
        lis302dl_write_1(sc, LIS302DL_REG_CTRL1, u8);

        /* reset the free-fall thresholds */
        lis302dl_write_1(sc, LIS302DL_REG_FF_WU_THS1, 0);
        lis302dl_write_1(sc, LIS302DL_REG_FF_WU_THS2, 0);

        /* reset the click thresholds */
        lis302dl_write_1(sc, LIS302DL_REG_CLICK_THSY_X, 0);
        lis302dl_write_1(sc, LIS302DL_REG_CLICK_THSZ, 0);

        iic_release_bus(sc->sc_i2c, 0);

        return 0;
}

static int
lis302dl_intr(void *v)
{
        struct lis302dl_softc *sc = v;
        uint8_t val;
        uint8_t u8;

        iic_acquire_bus(sc->sc_i2c, 0);

        if (lis302dl_read_1(sc, LIS302DL_REG_STATUS, &val) == 0) {
                if(val & LIS302DL_STATUS_XDA) {
                        lis302dl_read_1(sc, LIS302DL_REG_OUTX, &u8);
                        sc->sc_sensor[LIS302DL_SENSOR_XACCEL].value_cur = u8;
                }
                if(val & LIS302DL_STATUS_YDA) {
                        lis302dl_read_1(sc, LIS302DL_REG_OUTY, &u8);
                        sc->sc_sensor[LIS302DL_SENSOR_YACCEL].value_cur = u8;
                }
                if(val & LIS302DL_STATUS_ZDA) {
                        lis302dl_read_1(sc, LIS302DL_REG_OUTZ, &u8);
                        sc->sc_sensor[LIS302DL_SENSOR_ZACCEL].value_cur = u8;
                }
        }

        if (lis302dl_read_1(sc, LIS302DL_REG_FF_WU_SRC1, &val) == 0) {
                /* FIXME implement */
        }

        if (lis302dl_read_1(sc, LIS302DL_REG_FF_WU_SRC2, &val) == 0) {
                /* FIXME implement */
        }

        if (lis302dl_read_1(sc, LIS302DL_REG_CLICK_SRC, &val) == 0) {
                /* FIXME implement */
        }

        iic_release_bus(sc->sc_i2c, 0);

        return 0;
}

static int
lis302dl_read_1(struct lis302dl_softc *sc, uint8_t reg, uint8_t *val)
{
        return iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP, sc->sc_addr,
                        &reg, sizeof(reg), val, sizeof(*val), 0);
}

static int
lis302dl_write_1(struct lis302dl_softc *sc, uint8_t reg, uint8_t val)
{
        uint8_t data[2] = { reg, val };

        return iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
                        NULL, 0, data, sizeof(data), 0);
}


Home | Main Index | Thread Index | Old Index