forked from Minki/linux
fe0b980ffd
Set 10ms as minimum i2c slave configuration timeout since st_lsm6dsx relies on accel ODR for i2c master clock and at high sample rates (e.g. 833Hz or 416Hz) the slave sensor occasionally may need more cycles than i2c master timeout (2s/833Hz + 1 ~ 3ms) to apply the configuration resulting in an uncomplete slave configuration and a constant reading from the i2c slave connected to st_lsm6dsx i2c master. Fixes:8f9a5249e3
("iio: imu: st_lsm6dsx: enable 833Hz sample frequency for tagged sensors") Fixes:c91c1c844e
("iio: imu: st_lsm6dsx: add i2c embedded controller support") Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org> Cc: <Stable@vger.kernel.org> Link: https://lore.kernel.org/r/a69c8236bf16a1569966815ed71710af2722ed7d.1604247274.git.lorenzo@kernel.org Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
920 lines
22 KiB
C
920 lines
22 KiB
C
/*
|
|
* STMicroelectronics st_lsm6dsx i2c controller driver
|
|
*
|
|
* i2c controller embedded in lsm6dx series can connect up to four
|
|
* slave devices using accelerometer sensor as trigger for i2c
|
|
* read/write operations. Current implementation relies on SLV0 channel
|
|
* for slave configuration and SLV{1,2,3} to read data and push them into
|
|
* the hw FIFO
|
|
*
|
|
* Copyright (C) 2018 Lorenzo Bianconi <lorenzo.bianconi83@gmail.com>
|
|
*
|
|
* Permission to use, copy, modify, and/or 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 <linux/module.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/iio/iio.h>
|
|
#include <linux/iio/sysfs.h>
|
|
#include <linux/bitfield.h>
|
|
|
|
#include "st_lsm6dsx.h"
|
|
|
|
#define ST_LSM6DSX_SLV_ADDR(n, base) ((base) + (n) * 3)
|
|
#define ST_LSM6DSX_SLV_SUB_ADDR(n, base) ((base) + 1 + (n) * 3)
|
|
#define ST_LSM6DSX_SLV_CONFIG(n, base) ((base) + 2 + (n) * 3)
|
|
|
|
#define ST_LS6DSX_READ_OP_MASK GENMASK(2, 0)
|
|
|
|
static const struct st_lsm6dsx_ext_dev_settings st_lsm6dsx_ext_dev_table[] = {
|
|
/* LIS2MDL */
|
|
{
|
|
.i2c_addr = { 0x1e },
|
|
.wai = {
|
|
.addr = 0x4f,
|
|
.val = 0x40,
|
|
},
|
|
.id = ST_LSM6DSX_ID_MAGN,
|
|
.odr_table = {
|
|
.reg = {
|
|
.addr = 0x60,
|
|
.mask = GENMASK(3, 2),
|
|
},
|
|
.odr_avl[0] = { 10000, 0x0 },
|
|
.odr_avl[1] = { 20000, 0x1 },
|
|
.odr_avl[2] = { 50000, 0x2 },
|
|
.odr_avl[3] = { 100000, 0x3 },
|
|
.odr_len = 4,
|
|
},
|
|
.fs_table = {
|
|
.fs_avl[0] = {
|
|
.gain = 1500,
|
|
.val = 0x0,
|
|
}, /* 1500 uG/LSB */
|
|
.fs_len = 1,
|
|
},
|
|
.temp_comp = {
|
|
.addr = 0x60,
|
|
.mask = BIT(7),
|
|
},
|
|
.pwr_table = {
|
|
.reg = {
|
|
.addr = 0x60,
|
|
.mask = GENMASK(1, 0),
|
|
},
|
|
.off_val = 0x2,
|
|
.on_val = 0x0,
|
|
},
|
|
.off_canc = {
|
|
.addr = 0x61,
|
|
.mask = BIT(1),
|
|
},
|
|
.bdu = {
|
|
.addr = 0x62,
|
|
.mask = BIT(4),
|
|
},
|
|
.out = {
|
|
.addr = 0x68,
|
|
.len = 6,
|
|
},
|
|
},
|
|
/* LIS3MDL */
|
|
{
|
|
.i2c_addr = { 0x1e },
|
|
.wai = {
|
|
.addr = 0x0f,
|
|
.val = 0x3d,
|
|
},
|
|
.id = ST_LSM6DSX_ID_MAGN,
|
|
.odr_table = {
|
|
.reg = {
|
|
.addr = 0x20,
|
|
.mask = GENMASK(4, 2),
|
|
},
|
|
.odr_avl[0] = { 1000, 0x0 },
|
|
.odr_avl[1] = { 2000, 0x1 },
|
|
.odr_avl[2] = { 3000, 0x2 },
|
|
.odr_avl[3] = { 5000, 0x3 },
|
|
.odr_avl[4] = { 10000, 0x4 },
|
|
.odr_avl[5] = { 20000, 0x5 },
|
|
.odr_avl[6] = { 40000, 0x6 },
|
|
.odr_avl[7] = { 80000, 0x7 },
|
|
.odr_len = 8,
|
|
},
|
|
.fs_table = {
|
|
.reg = {
|
|
.addr = 0x21,
|
|
.mask = GENMASK(6, 5),
|
|
},
|
|
.fs_avl[0] = {
|
|
.gain = 146,
|
|
.val = 0x00,
|
|
}, /* 4000 uG/LSB */
|
|
.fs_avl[1] = {
|
|
.gain = 292,
|
|
.val = 0x01,
|
|
}, /* 8000 uG/LSB */
|
|
.fs_avl[2] = {
|
|
.gain = 438,
|
|
.val = 0x02,
|
|
}, /* 12000 uG/LSB */
|
|
.fs_avl[3] = {
|
|
.gain = 584,
|
|
.val = 0x03,
|
|
}, /* 16000 uG/LSB */
|
|
.fs_len = 4,
|
|
},
|
|
.pwr_table = {
|
|
.reg = {
|
|
.addr = 0x22,
|
|
.mask = GENMASK(1, 0),
|
|
},
|
|
.off_val = 0x2,
|
|
.on_val = 0x0,
|
|
},
|
|
.bdu = {
|
|
.addr = 0x24,
|
|
.mask = BIT(6),
|
|
},
|
|
.out = {
|
|
.addr = 0x28,
|
|
.len = 6,
|
|
},
|
|
},
|
|
};
|
|
|
|
static void st_lsm6dsx_shub_wait_complete(struct st_lsm6dsx_hw *hw)
|
|
{
|
|
struct st_lsm6dsx_sensor *sensor;
|
|
u32 odr, timeout;
|
|
|
|
sensor = iio_priv(hw->iio_devs[ST_LSM6DSX_ID_ACC]);
|
|
odr = (hw->enable_mask & BIT(ST_LSM6DSX_ID_ACC)) ? sensor->odr : 12500;
|
|
/* set 10ms as minimum timeout for i2c slave configuration */
|
|
timeout = max_t(u32, 2000000U / odr + 1, 10);
|
|
msleep(timeout);
|
|
}
|
|
|
|
/*
|
|
* st_lsm6dsx_shub_read_output - read i2c controller register
|
|
*
|
|
* Read st_lsm6dsx i2c controller register
|
|
*/
|
|
static int
|
|
st_lsm6dsx_shub_read_output(struct st_lsm6dsx_hw *hw, u8 *data,
|
|
int len)
|
|
{
|
|
const struct st_lsm6dsx_shub_settings *hub_settings;
|
|
int err;
|
|
|
|
mutex_lock(&hw->page_lock);
|
|
|
|
hub_settings = &hw->settings->shub_settings;
|
|
if (hub_settings->shub_out.sec_page) {
|
|
err = st_lsm6dsx_set_page(hw, true);
|
|
if (err < 0)
|
|
goto out;
|
|
}
|
|
|
|
err = regmap_bulk_read(hw->regmap, hub_settings->shub_out.addr,
|
|
data, len);
|
|
|
|
if (hub_settings->shub_out.sec_page)
|
|
st_lsm6dsx_set_page(hw, false);
|
|
out:
|
|
mutex_unlock(&hw->page_lock);
|
|
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* st_lsm6dsx_shub_write_reg - write i2c controller register
|
|
*
|
|
* Write st_lsm6dsx i2c controller register
|
|
*/
|
|
static int st_lsm6dsx_shub_write_reg(struct st_lsm6dsx_hw *hw, u8 addr,
|
|
u8 *data, int len)
|
|
{
|
|
int err;
|
|
|
|
mutex_lock(&hw->page_lock);
|
|
err = st_lsm6dsx_set_page(hw, true);
|
|
if (err < 0)
|
|
goto out;
|
|
|
|
err = regmap_bulk_write(hw->regmap, addr, data, len);
|
|
|
|
st_lsm6dsx_set_page(hw, false);
|
|
out:
|
|
mutex_unlock(&hw->page_lock);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
st_lsm6dsx_shub_write_reg_with_mask(struct st_lsm6dsx_hw *hw, u8 addr,
|
|
u8 mask, u8 val)
|
|
{
|
|
int err;
|
|
|
|
mutex_lock(&hw->page_lock);
|
|
err = st_lsm6dsx_set_page(hw, true);
|
|
if (err < 0)
|
|
goto out;
|
|
|
|
err = regmap_update_bits(hw->regmap, addr, mask, val);
|
|
|
|
st_lsm6dsx_set_page(hw, false);
|
|
out:
|
|
mutex_unlock(&hw->page_lock);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int st_lsm6dsx_shub_master_enable(struct st_lsm6dsx_sensor *sensor,
|
|
bool enable)
|
|
{
|
|
const struct st_lsm6dsx_shub_settings *hub_settings;
|
|
struct st_lsm6dsx_hw *hw = sensor->hw;
|
|
unsigned int data;
|
|
int err;
|
|
|
|
/* enable acc sensor as trigger */
|
|
err = st_lsm6dsx_sensor_set_enable(sensor, enable);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
mutex_lock(&hw->page_lock);
|
|
|
|
hub_settings = &hw->settings->shub_settings;
|
|
if (hub_settings->master_en.sec_page) {
|
|
err = st_lsm6dsx_set_page(hw, true);
|
|
if (err < 0)
|
|
goto out;
|
|
}
|
|
|
|
data = ST_LSM6DSX_SHIFT_VAL(enable, hub_settings->master_en.mask);
|
|
err = regmap_update_bits(hw->regmap, hub_settings->master_en.addr,
|
|
hub_settings->master_en.mask, data);
|
|
|
|
if (hub_settings->master_en.sec_page)
|
|
st_lsm6dsx_set_page(hw, false);
|
|
out:
|
|
mutex_unlock(&hw->page_lock);
|
|
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* st_lsm6dsx_shub_read - read data from slave device register
|
|
*
|
|
* Read data from slave device register. SLV0 is used for
|
|
* one-shot read operation
|
|
*/
|
|
static int
|
|
st_lsm6dsx_shub_read(struct st_lsm6dsx_sensor *sensor, u8 addr,
|
|
u8 *data, int len)
|
|
{
|
|
const struct st_lsm6dsx_shub_settings *hub_settings;
|
|
u8 config[3], slv_addr, slv_config = 0;
|
|
struct st_lsm6dsx_hw *hw = sensor->hw;
|
|
const struct st_lsm6dsx_reg *aux_sens;
|
|
int err;
|
|
|
|
hub_settings = &hw->settings->shub_settings;
|
|
slv_addr = ST_LSM6DSX_SLV_ADDR(0, hub_settings->slv0_addr);
|
|
aux_sens = &hw->settings->shub_settings.aux_sens;
|
|
/* do not overwrite aux_sens */
|
|
if (slv_addr + 2 == aux_sens->addr)
|
|
slv_config = ST_LSM6DSX_SHIFT_VAL(3, aux_sens->mask);
|
|
|
|
config[0] = (sensor->ext_info.addr << 1) | 1;
|
|
config[1] = addr;
|
|
config[2] = (len & ST_LS6DSX_READ_OP_MASK) | slv_config;
|
|
|
|
err = st_lsm6dsx_shub_write_reg(hw, slv_addr, config,
|
|
sizeof(config));
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = st_lsm6dsx_shub_master_enable(sensor, true);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
st_lsm6dsx_shub_wait_complete(hw);
|
|
|
|
err = st_lsm6dsx_shub_read_output(hw, data,
|
|
len & ST_LS6DSX_READ_OP_MASK);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
st_lsm6dsx_shub_master_enable(sensor, false);
|
|
|
|
config[0] = hub_settings->pause;
|
|
config[1] = 0;
|
|
config[2] = slv_config;
|
|
return st_lsm6dsx_shub_write_reg(hw, slv_addr, config,
|
|
sizeof(config));
|
|
}
|
|
|
|
/*
|
|
* st_lsm6dsx_shub_write - write data to slave device register
|
|
*
|
|
* Write data from slave device register. SLV0 is used for
|
|
* one-shot write operation
|
|
*/
|
|
static int
|
|
st_lsm6dsx_shub_write(struct st_lsm6dsx_sensor *sensor, u8 addr,
|
|
u8 *data, int len)
|
|
{
|
|
const struct st_lsm6dsx_shub_settings *hub_settings;
|
|
struct st_lsm6dsx_hw *hw = sensor->hw;
|
|
u8 config[2], slv_addr;
|
|
int err, i;
|
|
|
|
hub_settings = &hw->settings->shub_settings;
|
|
if (hub_settings->wr_once.addr) {
|
|
unsigned int data;
|
|
|
|
data = ST_LSM6DSX_SHIFT_VAL(1, hub_settings->wr_once.mask);
|
|
err = st_lsm6dsx_shub_write_reg_with_mask(hw,
|
|
hub_settings->wr_once.addr,
|
|
hub_settings->wr_once.mask,
|
|
data);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
|
|
slv_addr = ST_LSM6DSX_SLV_ADDR(0, hub_settings->slv0_addr);
|
|
config[0] = sensor->ext_info.addr << 1;
|
|
for (i = 0 ; i < len; i++) {
|
|
config[1] = addr + i;
|
|
|
|
err = st_lsm6dsx_shub_write_reg(hw, slv_addr, config,
|
|
sizeof(config));
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = st_lsm6dsx_shub_write_reg(hw, hub_settings->dw_slv0_addr,
|
|
&data[i], 1);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = st_lsm6dsx_shub_master_enable(sensor, true);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
st_lsm6dsx_shub_wait_complete(hw);
|
|
|
|
st_lsm6dsx_shub_master_enable(sensor, false);
|
|
}
|
|
|
|
config[0] = hub_settings->pause;
|
|
config[1] = 0;
|
|
return st_lsm6dsx_shub_write_reg(hw, slv_addr, config, sizeof(config));
|
|
}
|
|
|
|
static int
|
|
st_lsm6dsx_shub_write_with_mask(struct st_lsm6dsx_sensor *sensor,
|
|
u8 addr, u8 mask, u8 val)
|
|
{
|
|
int err;
|
|
u8 data;
|
|
|
|
err = st_lsm6dsx_shub_read(sensor, addr, &data, sizeof(data));
|
|
if (err < 0)
|
|
return err;
|
|
|
|
data = ((data & ~mask) | (val << __ffs(mask) & mask));
|
|
|
|
return st_lsm6dsx_shub_write(sensor, addr, &data, sizeof(data));
|
|
}
|
|
|
|
static int
|
|
st_lsm6dsx_shub_get_odr_val(struct st_lsm6dsx_sensor *sensor,
|
|
u32 odr, u16 *val)
|
|
{
|
|
const struct st_lsm6dsx_ext_dev_settings *settings;
|
|
int i;
|
|
|
|
settings = sensor->ext_info.settings;
|
|
for (i = 0; i < settings->odr_table.odr_len; i++) {
|
|
if (settings->odr_table.odr_avl[i].milli_hz == odr)
|
|
break;
|
|
}
|
|
|
|
if (i == settings->odr_table.odr_len)
|
|
return -EINVAL;
|
|
|
|
*val = settings->odr_table.odr_avl[i].val;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
st_lsm6dsx_shub_set_odr(struct st_lsm6dsx_sensor *sensor, u32 odr)
|
|
{
|
|
const struct st_lsm6dsx_ext_dev_settings *settings;
|
|
u16 val;
|
|
int err;
|
|
|
|
err = st_lsm6dsx_shub_get_odr_val(sensor, odr, &val);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
settings = sensor->ext_info.settings;
|
|
return st_lsm6dsx_shub_write_with_mask(sensor,
|
|
settings->odr_table.reg.addr,
|
|
settings->odr_table.reg.mask,
|
|
val);
|
|
}
|
|
|
|
/* use SLV{1,2,3} for FIFO read operations */
|
|
static int
|
|
st_lsm6dsx_shub_config_channels(struct st_lsm6dsx_sensor *sensor,
|
|
bool enable)
|
|
{
|
|
const struct st_lsm6dsx_shub_settings *hub_settings;
|
|
const struct st_lsm6dsx_ext_dev_settings *settings;
|
|
u8 config[9] = {}, enable_mask, slv_addr;
|
|
struct st_lsm6dsx_hw *hw = sensor->hw;
|
|
struct st_lsm6dsx_sensor *cur_sensor;
|
|
int i, j = 0;
|
|
|
|
hub_settings = &hw->settings->shub_settings;
|
|
if (enable)
|
|
enable_mask = hw->enable_mask | BIT(sensor->id);
|
|
else
|
|
enable_mask = hw->enable_mask & ~BIT(sensor->id);
|
|
|
|
for (i = ST_LSM6DSX_ID_EXT0; i <= ST_LSM6DSX_ID_EXT2; i++) {
|
|
if (!hw->iio_devs[i])
|
|
continue;
|
|
|
|
cur_sensor = iio_priv(hw->iio_devs[i]);
|
|
if (!(enable_mask & BIT(cur_sensor->id)))
|
|
continue;
|
|
|
|
settings = cur_sensor->ext_info.settings;
|
|
config[j] = (sensor->ext_info.addr << 1) | 1;
|
|
config[j + 1] = settings->out.addr;
|
|
config[j + 2] = (settings->out.len & ST_LS6DSX_READ_OP_MASK) |
|
|
hub_settings->batch_en;
|
|
j += 3;
|
|
}
|
|
|
|
slv_addr = ST_LSM6DSX_SLV_ADDR(1, hub_settings->slv0_addr);
|
|
return st_lsm6dsx_shub_write_reg(hw, slv_addr, config,
|
|
sizeof(config));
|
|
}
|
|
|
|
int st_lsm6dsx_shub_set_enable(struct st_lsm6dsx_sensor *sensor, bool enable)
|
|
{
|
|
const struct st_lsm6dsx_ext_dev_settings *settings;
|
|
int err;
|
|
|
|
err = st_lsm6dsx_shub_config_channels(sensor, enable);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
settings = sensor->ext_info.settings;
|
|
if (enable) {
|
|
err = st_lsm6dsx_shub_set_odr(sensor,
|
|
sensor->ext_info.slv_odr);
|
|
if (err < 0)
|
|
return err;
|
|
} else {
|
|
err = st_lsm6dsx_shub_write_with_mask(sensor,
|
|
settings->odr_table.reg.addr,
|
|
settings->odr_table.reg.mask, 0);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
|
|
if (settings->pwr_table.reg.addr) {
|
|
u8 val;
|
|
|
|
val = enable ? settings->pwr_table.on_val
|
|
: settings->pwr_table.off_val;
|
|
err = st_lsm6dsx_shub_write_with_mask(sensor,
|
|
settings->pwr_table.reg.addr,
|
|
settings->pwr_table.reg.mask, val);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
|
|
return st_lsm6dsx_shub_master_enable(sensor, enable);
|
|
}
|
|
|
|
static int
|
|
st_lsm6dsx_shub_read_oneshot(struct st_lsm6dsx_sensor *sensor,
|
|
struct iio_chan_spec const *ch,
|
|
int *val)
|
|
{
|
|
int err, delay, len;
|
|
u8 data[4];
|
|
|
|
err = st_lsm6dsx_shub_set_enable(sensor, true);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
delay = 1000000000 / sensor->ext_info.slv_odr;
|
|
usleep_range(delay, 2 * delay);
|
|
|
|
len = min_t(int, sizeof(data), ch->scan_type.realbits >> 3);
|
|
err = st_lsm6dsx_shub_read(sensor, ch->address, data, len);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = st_lsm6dsx_shub_set_enable(sensor, false);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
switch (len) {
|
|
case 2:
|
|
*val = (s16)le16_to_cpu(*((__le16 *)data));
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return IIO_VAL_INT;
|
|
}
|
|
|
|
static int
|
|
st_lsm6dsx_shub_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);
|
|
int ret;
|
|
|
|
switch (mask) {
|
|
case IIO_CHAN_INFO_RAW:
|
|
ret = iio_device_claim_direct_mode(iio_dev);
|
|
if (ret)
|
|
break;
|
|
|
|
ret = st_lsm6dsx_shub_read_oneshot(sensor, ch, val);
|
|
iio_device_release_direct_mode(iio_dev);
|
|
break;
|
|
case IIO_CHAN_INFO_SAMP_FREQ:
|
|
*val = sensor->ext_info.slv_odr / 1000;
|
|
*val2 = (sensor->ext_info.slv_odr % 1000) * 1000;
|
|
ret = IIO_VAL_INT_PLUS_MICRO;
|
|
break;
|
|
case IIO_CHAN_INFO_SCALE:
|
|
*val = 0;
|
|
*val2 = sensor->gain;
|
|
ret = IIO_VAL_INT_PLUS_MICRO;
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
st_lsm6dsx_shub_set_full_scale(struct st_lsm6dsx_sensor *sensor,
|
|
u32 gain)
|
|
{
|
|
const struct st_lsm6dsx_fs_table_entry *fs_table;
|
|
int i, err;
|
|
|
|
fs_table = &sensor->ext_info.settings->fs_table;
|
|
if (!fs_table->reg.addr)
|
|
return -ENOTSUPP;
|
|
|
|
for (i = 0; i < fs_table->fs_len; i++) {
|
|
if (fs_table->fs_avl[i].gain == gain)
|
|
break;
|
|
}
|
|
|
|
if (i == fs_table->fs_len)
|
|
return -EINVAL;
|
|
|
|
err = st_lsm6dsx_shub_write_with_mask(sensor, fs_table->reg.addr,
|
|
fs_table->reg.mask,
|
|
fs_table->fs_avl[i].val);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
sensor->gain = gain;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
st_lsm6dsx_shub_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);
|
|
int err;
|
|
|
|
err = iio_device_claim_direct_mode(iio_dev);
|
|
if (err)
|
|
return err;
|
|
|
|
switch (mask) {
|
|
case IIO_CHAN_INFO_SAMP_FREQ: {
|
|
u16 data;
|
|
|
|
val = val * 1000 + val2 / 1000;
|
|
err = st_lsm6dsx_shub_get_odr_val(sensor, val, &data);
|
|
if (!err) {
|
|
struct st_lsm6dsx_hw *hw = sensor->hw;
|
|
struct st_lsm6dsx_sensor *ref_sensor;
|
|
u8 odr_val;
|
|
int odr;
|
|
|
|
ref_sensor = iio_priv(hw->iio_devs[ST_LSM6DSX_ID_ACC]);
|
|
odr = st_lsm6dsx_check_odr(ref_sensor, val, &odr_val);
|
|
if (odr < 0) {
|
|
err = odr;
|
|
goto release;
|
|
}
|
|
|
|
sensor->ext_info.slv_odr = val;
|
|
sensor->odr = odr;
|
|
}
|
|
break;
|
|
}
|
|
case IIO_CHAN_INFO_SCALE:
|
|
err = st_lsm6dsx_shub_set_full_scale(sensor, val2);
|
|
break;
|
|
default:
|
|
err = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
release:
|
|
iio_device_release_direct_mode(iio_dev);
|
|
|
|
return err;
|
|
}
|
|
|
|
static ssize_t
|
|
st_lsm6dsx_shub_sampling_freq_avail(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct st_lsm6dsx_sensor *sensor = iio_priv(dev_get_drvdata(dev));
|
|
const struct st_lsm6dsx_ext_dev_settings *settings;
|
|
int i, len = 0;
|
|
|
|
settings = sensor->ext_info.settings;
|
|
for (i = 0; i < settings->odr_table.odr_len; i++) {
|
|
u32 val = settings->odr_table.odr_avl[i].milli_hz;
|
|
|
|
len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%03d ",
|
|
val / 1000, val % 1000);
|
|
}
|
|
buf[len - 1] = '\n';
|
|
|
|
return len;
|
|
}
|
|
|
|
static ssize_t st_lsm6dsx_shub_scale_avail(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct st_lsm6dsx_sensor *sensor = iio_priv(dev_get_drvdata(dev));
|
|
const struct st_lsm6dsx_ext_dev_settings *settings;
|
|
int i, len = 0;
|
|
|
|
settings = sensor->ext_info.settings;
|
|
for (i = 0; i < settings->fs_table.fs_len; i++)
|
|
len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06u ",
|
|
settings->fs_table.fs_avl[i].gain);
|
|
buf[len - 1] = '\n';
|
|
|
|
return len;
|
|
}
|
|
|
|
static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(st_lsm6dsx_shub_sampling_freq_avail);
|
|
static IIO_DEVICE_ATTR(in_scale_available, 0444,
|
|
st_lsm6dsx_shub_scale_avail, NULL, 0);
|
|
static struct attribute *st_lsm6dsx_ext_attributes[] = {
|
|
&iio_dev_attr_sampling_frequency_available.dev_attr.attr,
|
|
&iio_dev_attr_in_scale_available.dev_attr.attr,
|
|
NULL,
|
|
};
|
|
|
|
static const struct attribute_group st_lsm6dsx_ext_attribute_group = {
|
|
.attrs = st_lsm6dsx_ext_attributes,
|
|
};
|
|
|
|
static const struct iio_info st_lsm6dsx_ext_info = {
|
|
.attrs = &st_lsm6dsx_ext_attribute_group,
|
|
.read_raw = st_lsm6dsx_shub_read_raw,
|
|
.write_raw = st_lsm6dsx_shub_write_raw,
|
|
.hwfifo_set_watermark = st_lsm6dsx_set_watermark,
|
|
};
|
|
|
|
static struct iio_dev *
|
|
st_lsm6dsx_shub_alloc_iiodev(struct st_lsm6dsx_hw *hw,
|
|
enum st_lsm6dsx_sensor_id id,
|
|
const struct st_lsm6dsx_ext_dev_settings *info,
|
|
u8 i2c_addr, const char *name)
|
|
{
|
|
enum st_lsm6dsx_sensor_id ref_id = ST_LSM6DSX_ID_ACC;
|
|
struct iio_chan_spec *ext_channels;
|
|
struct st_lsm6dsx_sensor *sensor;
|
|
struct iio_dev *iio_dev;
|
|
|
|
iio_dev = devm_iio_device_alloc(hw->dev, sizeof(*sensor));
|
|
if (!iio_dev)
|
|
return NULL;
|
|
|
|
iio_dev->modes = INDIO_DIRECT_MODE;
|
|
iio_dev->info = &st_lsm6dsx_ext_info;
|
|
|
|
sensor = iio_priv(iio_dev);
|
|
sensor->id = id;
|
|
sensor->hw = hw;
|
|
sensor->odr = hw->settings->odr_table[ref_id].odr_avl[0].milli_hz;
|
|
sensor->ext_info.slv_odr = info->odr_table.odr_avl[0].milli_hz;
|
|
sensor->gain = info->fs_table.fs_avl[0].gain;
|
|
sensor->ext_info.settings = info;
|
|
sensor->ext_info.addr = i2c_addr;
|
|
sensor->watermark = 1;
|
|
|
|
switch (info->id) {
|
|
case ST_LSM6DSX_ID_MAGN: {
|
|
const struct iio_chan_spec magn_channels[] = {
|
|
ST_LSM6DSX_CHANNEL(IIO_MAGN, info->out.addr,
|
|
IIO_MOD_X, 0),
|
|
ST_LSM6DSX_CHANNEL(IIO_MAGN, info->out.addr + 2,
|
|
IIO_MOD_Y, 1),
|
|
ST_LSM6DSX_CHANNEL(IIO_MAGN, info->out.addr + 4,
|
|
IIO_MOD_Z, 2),
|
|
IIO_CHAN_SOFT_TIMESTAMP(3),
|
|
};
|
|
|
|
ext_channels = devm_kzalloc(hw->dev, sizeof(magn_channels),
|
|
GFP_KERNEL);
|
|
if (!ext_channels)
|
|
return NULL;
|
|
|
|
memcpy(ext_channels, magn_channels, sizeof(magn_channels));
|
|
iio_dev->available_scan_masks = st_lsm6dsx_available_scan_masks;
|
|
iio_dev->channels = ext_channels;
|
|
iio_dev->num_channels = ARRAY_SIZE(magn_channels);
|
|
|
|
scnprintf(sensor->name, sizeof(sensor->name), "%s_magn",
|
|
name);
|
|
break;
|
|
}
|
|
default:
|
|
return NULL;
|
|
}
|
|
iio_dev->name = sensor->name;
|
|
|
|
return iio_dev;
|
|
}
|
|
|
|
static int st_lsm6dsx_shub_init_device(struct st_lsm6dsx_sensor *sensor)
|
|
{
|
|
const struct st_lsm6dsx_ext_dev_settings *settings;
|
|
int err;
|
|
|
|
settings = sensor->ext_info.settings;
|
|
if (settings->bdu.addr) {
|
|
err = st_lsm6dsx_shub_write_with_mask(sensor,
|
|
settings->bdu.addr,
|
|
settings->bdu.mask, 1);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
|
|
if (settings->temp_comp.addr) {
|
|
err = st_lsm6dsx_shub_write_with_mask(sensor,
|
|
settings->temp_comp.addr,
|
|
settings->temp_comp.mask, 1);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
|
|
if (settings->off_canc.addr) {
|
|
err = st_lsm6dsx_shub_write_with_mask(sensor,
|
|
settings->off_canc.addr,
|
|
settings->off_canc.mask, 1);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
st_lsm6dsx_shub_check_wai(struct st_lsm6dsx_hw *hw, u8 *i2c_addr,
|
|
const struct st_lsm6dsx_ext_dev_settings *settings)
|
|
{
|
|
const struct st_lsm6dsx_shub_settings *hub_settings;
|
|
u8 config[3], data, slv_addr, slv_config = 0;
|
|
const struct st_lsm6dsx_reg *aux_sens;
|
|
struct st_lsm6dsx_sensor *sensor;
|
|
bool found = false;
|
|
int i, err;
|
|
|
|
sensor = iio_priv(hw->iio_devs[ST_LSM6DSX_ID_ACC]);
|
|
hub_settings = &hw->settings->shub_settings;
|
|
aux_sens = &hw->settings->shub_settings.aux_sens;
|
|
slv_addr = ST_LSM6DSX_SLV_ADDR(0, hub_settings->slv0_addr);
|
|
/* do not overwrite aux_sens */
|
|
if (slv_addr + 2 == aux_sens->addr)
|
|
slv_config = ST_LSM6DSX_SHIFT_VAL(3, aux_sens->mask);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(settings->i2c_addr); i++) {
|
|
if (!settings->i2c_addr[i])
|
|
continue;
|
|
|
|
/* read wai slave register */
|
|
config[0] = (settings->i2c_addr[i] << 1) | 0x1;
|
|
config[1] = settings->wai.addr;
|
|
config[2] = 0x1 | slv_config;
|
|
|
|
err = st_lsm6dsx_shub_write_reg(hw, slv_addr, config,
|
|
sizeof(config));
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = st_lsm6dsx_shub_master_enable(sensor, true);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
st_lsm6dsx_shub_wait_complete(hw);
|
|
|
|
err = st_lsm6dsx_shub_read_output(hw, &data, sizeof(data));
|
|
|
|
st_lsm6dsx_shub_master_enable(sensor, false);
|
|
|
|
if (err < 0)
|
|
return err;
|
|
|
|
if (data != settings->wai.val)
|
|
continue;
|
|
|
|
*i2c_addr = settings->i2c_addr[i];
|
|
found = true;
|
|
break;
|
|
}
|
|
|
|
/* reset SLV0 channel */
|
|
config[0] = hub_settings->pause;
|
|
config[1] = 0;
|
|
config[2] = slv_config;
|
|
err = st_lsm6dsx_shub_write_reg(hw, slv_addr, config,
|
|
sizeof(config));
|
|
if (err < 0)
|
|
return err;
|
|
|
|
return found ? 0 : -ENODEV;
|
|
}
|
|
|
|
int st_lsm6dsx_shub_probe(struct st_lsm6dsx_hw *hw, const char *name)
|
|
{
|
|
enum st_lsm6dsx_sensor_id id = ST_LSM6DSX_ID_EXT0;
|
|
struct st_lsm6dsx_sensor *sensor;
|
|
int err, i, num_ext_dev = 0;
|
|
u8 i2c_addr = 0;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(st_lsm6dsx_ext_dev_table); i++) {
|
|
err = st_lsm6dsx_shub_check_wai(hw, &i2c_addr,
|
|
&st_lsm6dsx_ext_dev_table[i]);
|
|
if (err == -ENODEV)
|
|
continue;
|
|
else if (err < 0)
|
|
return err;
|
|
|
|
hw->iio_devs[id] = st_lsm6dsx_shub_alloc_iiodev(hw, id,
|
|
&st_lsm6dsx_ext_dev_table[i],
|
|
i2c_addr, name);
|
|
if (!hw->iio_devs[id])
|
|
return -ENOMEM;
|
|
|
|
sensor = iio_priv(hw->iio_devs[id]);
|
|
err = st_lsm6dsx_shub_init_device(sensor);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
if (++num_ext_dev >= hw->settings->shub_settings.num_ext_dev)
|
|
break;
|
|
id++;
|
|
}
|
|
|
|
return 0;
|
|
}
|