#include <linux/cleanup.h>
#include <linux/errno.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/mutex.h>
#include <linux/regmap.h>
#include <linux/sprintf.h>
#include <linux/types.h>
#include <linux/units.h>
#include "st_lsm6dsx.h"
static int
st_lsm6dsx_fusion_get_odr_val(const struct st_lsm6dsx_fusion_settings *settings,
u32 odr_mHz, u8 *val)
{
int odr_hz = odr_mHz / MILLI;
int i;
for (i = 0; i < settings->odr_len; i++) {
if (settings->odr_hz[i] == odr_hz)
break;
}
if (i == settings->odr_len)
return -EINVAL;
*val = i;
return 0;
}
static int st_lsm6dsx_fusion_page_enable(struct st_lsm6dsx_hw *hw)
{
const struct st_lsm6dsx_reg *mux;
mux = &hw->settings->fusion_settings.page_mux;
return regmap_set_bits(hw->regmap, mux->addr, mux->mask);
}
static int st_lsm6dsx_fusion_page_disable(struct st_lsm6dsx_hw *hw)
{
const struct st_lsm6dsx_reg *mux;
mux = &hw->settings->fusion_settings.page_mux;
return regmap_clear_bits(hw->regmap, mux->addr, mux->mask);
}
static int st_lsm6dsx_fusion_set_odr_locked(struct st_lsm6dsx_sensor *sensor,
bool enable)
{
const struct st_lsm6dsx_fusion_settings *settings;
struct st_lsm6dsx_hw *hw = sensor->hw;
int err;
settings = &hw->settings->fusion_settings;
if (enable) {
const struct st_lsm6dsx_reg *reg = &settings->odr_reg;
u8 odr_val;
u8 data;
st_lsm6dsx_fusion_get_odr_val(settings, sensor->hwfifo_odr_mHz,
&odr_val);
data = ST_LSM6DSX_SHIFT_VAL(odr_val, reg->mask);
err = regmap_update_bits(hw->regmap, reg->addr, reg->mask,
data);
if (err)
return err;
}
return regmap_assign_bits(hw->regmap, settings->fifo_enable.addr,
settings->fifo_enable.mask, enable);
}
int st_lsm6dsx_fusion_set_enable(struct st_lsm6dsx_sensor *sensor, bool enable)
{
struct st_lsm6dsx_hw *hw = sensor->hw;
const struct st_lsm6dsx_reg *en_reg;
int err;
guard(mutex)(&hw->page_lock);
en_reg = &hw->settings->fusion_settings.enable;
err = st_lsm6dsx_fusion_page_enable(hw);
if (err)
return err;
err = regmap_assign_bits(hw->regmap, en_reg->addr, en_reg->mask, enable);
if (err) {
st_lsm6dsx_fusion_page_disable(hw);
return err;
}
return st_lsm6dsx_fusion_page_disable(hw);
}
int st_lsm6dsx_fusion_set_odr(struct st_lsm6dsx_sensor *sensor, bool enable)
{
struct st_lsm6dsx_hw *hw = sensor->hw;
int err;
guard(mutex)(&hw->page_lock);
err = st_lsm6dsx_fusion_page_enable(hw);
if (err)
return err;
err = st_lsm6dsx_fusion_set_odr_locked(sensor, enable);
if (err) {
st_lsm6dsx_fusion_page_disable(hw);
return err;
}
return st_lsm6dsx_fusion_page_disable(hw);
}
static int st_lsm6dsx_fusion_read_raw(struct iio_dev *iio_dev,
struct iio_chan_spec const *ch,
int *val, int *val2, long mask)
{
struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev);
switch (mask) {
case IIO_CHAN_INFO_SAMP_FREQ:
*val = sensor->hwfifo_odr_mHz / MILLI;
*val2 = (sensor->hwfifo_odr_mHz % MILLI) * (MICRO / MILLI);
return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
}
static int st_lsm6dsx_fusion_write_raw(struct iio_dev *iio_dev,
struct iio_chan_spec const *chan,
int val, int val2, long mask)
{
struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev);
const struct st_lsm6dsx_fusion_settings *settings;
int err;
settings = &sensor->hw->settings->fusion_settings;
switch (mask) {
case IIO_CHAN_INFO_SAMP_FREQ: {
u32 odr_mHz = val * MILLI + val2 * (MILLI / MICRO);
u8 odr_val;
err = st_lsm6dsx_fusion_get_odr_val(settings, odr_mHz, &odr_val);
if (err)
return err;
sensor->hwfifo_odr_mHz = odr_mHz;
return 0;
}
default:
return -EINVAL;
}
}
static int st_lsm6dsx_fusion_read_avail(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
const int **vals, int *type,
int *length, long mask)
{
struct st_lsm6dsx_sensor *sensor = iio_priv(indio_dev);
const struct st_lsm6dsx_fusion_settings *settings;
settings = &sensor->hw->settings->fusion_settings;
switch (mask) {
case IIO_CHAN_INFO_SAMP_FREQ:
*vals = settings->odr_hz;
*type = IIO_VAL_INT;
*length = settings->odr_len;
return IIO_AVAIL_LIST;
default:
return -EINVAL;
}
}
static const struct iio_info st_lsm6dsx_fusion_info = {
.read_raw = st_lsm6dsx_fusion_read_raw,
.read_avail = st_lsm6dsx_fusion_read_avail,
.write_raw = st_lsm6dsx_fusion_write_raw,
.hwfifo_set_watermark = st_lsm6dsx_set_watermark,
};
int st_lsm6dsx_fusion_probe(struct st_lsm6dsx_hw *hw, const char *name)
{
const struct st_lsm6dsx_fusion_settings *settings;
struct st_lsm6dsx_sensor *sensor;
struct iio_dev *iio_dev;
int ret;
iio_dev = devm_iio_device_alloc(hw->dev, sizeof(*sensor));
if (!iio_dev)
return -ENOMEM;
settings = &hw->settings->fusion_settings;
sensor = iio_priv(iio_dev);
sensor->id = ST_LSM6DSX_ID_FUSION;
sensor->hw = hw;
sensor->hwfifo_odr_mHz = settings->odr_hz[0] * MILLI;
sensor->watermark = 1;
iio_dev->modes = INDIO_DIRECT_MODE;
iio_dev->info = &st_lsm6dsx_fusion_info;
iio_dev->channels = settings->chan;
iio_dev->num_channels = settings->chan_len;
ret = snprintf(sensor->name, sizeof(sensor->name), "%s_fusion", name);
if (ret >= sizeof(sensor->name))
return -E2BIG;
iio_dev->name = sensor->name;
hw->iio_devs[ST_LSM6DSX_ID_FUSION] = iio_dev;
return 0;
}