diff options
Diffstat (limited to 'drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c')
-rw-r--r-- | drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c | 434 |
1 files changed, 430 insertions, 4 deletions
diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index 2d3495560136..8a813ddba19c 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -48,8 +48,11 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/delay.h> +#include <linux/iio/events.h> #include <linux/iio/iio.h> #include <linux/iio/sysfs.h> +#include <linux/interrupt.h> +#include <linux/irq.h> #include <linux/pm.h> #include <linux/regmap.h> #include <linux/bitfield.h> @@ -65,10 +68,15 @@ #define ST_LSM6DSX_REG_BDU_ADDR 0x12 #define ST_LSM6DSX_REG_BDU_MASK BIT(6) +#define ST_LSM6DSX_REG_HLACTIVE_ADDR 0x12 +#define ST_LSM6DSX_REG_HLACTIVE_MASK BIT(5) +#define ST_LSM6DSX_REG_PP_OD_ADDR 0x12 +#define ST_LSM6DSX_REG_PP_OD_MASK BIT(4) + static const struct iio_chan_spec st_lsm6dsx_acc_channels[] = { - ST_LSM6DSX_CHANNEL(IIO_ACCEL, 0x28, IIO_MOD_X, 0), - ST_LSM6DSX_CHANNEL(IIO_ACCEL, 0x2a, IIO_MOD_Y, 1), - ST_LSM6DSX_CHANNEL(IIO_ACCEL, 0x2c, IIO_MOD_Z, 2), + ST_LSM6DSX_CHANNEL_ACC(IIO_ACCEL, 0x28, IIO_MOD_X, 0), + ST_LSM6DSX_CHANNEL_ACC(IIO_ACCEL, 0x2a, IIO_MOD_Y, 1), + ST_LSM6DSX_CHANNEL_ACC(IIO_ACCEL, 0x2c, IIO_MOD_Z, 2), IIO_CHAN_SOFT_TIMESTAMP(3), }; @@ -161,6 +169,9 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .wai = 0x69, .int1_addr = 0x0d, .int2_addr = 0x0e, + .int1_func_addr = 0x5e, + .int2_func_addr = 0x5f, + .int_func_mask = BIT(5), .reset_addr = 0x12, .max_fifo_size = 1365, .id = { @@ -237,6 +248,10 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .mask = GENMASK(5, 3), }, }, + .lir = { + .addr = 0x58, + .mask = BIT(0), + }, .fifo_ops = { .update_fifo = st_lsm6dsx_update_fifo, .read_fifo = st_lsm6dsx_read_fifo, @@ -268,11 +283,25 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .mask = GENMASK(5, 3), }, }, + .event_settings = { + .wakeup_reg = { + .addr = 0x5B, + .mask = GENMASK(5, 0), + }, + .wakeup_src_reg = 0x1b, + .wakeup_src_status_mask = BIT(3), + .wakeup_src_z_mask = BIT(0), + .wakeup_src_y_mask = BIT(1), + .wakeup_src_x_mask = BIT(2), + }, }, { .wai = 0x69, .int1_addr = 0x0d, .int2_addr = 0x0e, + .int1_func_addr = 0x5e, + .int2_func_addr = 0x5f, + .int_func_mask = BIT(5), .reset_addr = 0x12, .max_fifo_size = 682, .id = { @@ -349,6 +378,10 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .mask = GENMASK(5, 3), }, }, + .lir = { + .addr = 0x58, + .mask = BIT(0), + }, .fifo_ops = { .update_fifo = st_lsm6dsx_update_fifo, .read_fifo = st_lsm6dsx_read_fifo, @@ -380,11 +413,25 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .mask = GENMASK(5, 3), }, }, + .event_settings = { + .wakeup_reg = { + .addr = 0x5B, + .mask = GENMASK(5, 0), + }, + .wakeup_src_reg = 0x1b, + .wakeup_src_status_mask = BIT(3), + .wakeup_src_z_mask = BIT(0), + .wakeup_src_y_mask = BIT(1), + .wakeup_src_x_mask = BIT(2), + }, }, { .wai = 0x6a, .int1_addr = 0x0d, .int2_addr = 0x0e, + .int1_func_addr = 0x5e, + .int2_func_addr = 0x5f, + .int_func_mask = BIT(5), .reset_addr = 0x12, .max_fifo_size = 682, .id = { @@ -470,6 +517,10 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .mask = GENMASK(5, 3), }, }, + .lir = { + .addr = 0x58, + .mask = BIT(0), + }, .fifo_ops = { .update_fifo = st_lsm6dsx_update_fifo, .read_fifo = st_lsm6dsx_read_fifo, @@ -501,6 +552,21 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .mask = GENMASK(5, 3), }, }, + .event_settings = { + .enable_reg = { + .addr = 0x58, + .mask = BIT(7), + }, + .wakeup_reg = { + .addr = 0x5B, + .mask = GENMASK(5, 0), + }, + .wakeup_src_reg = 0x1b, + .wakeup_src_status_mask = BIT(3), + .wakeup_src_z_mask = BIT(0), + .wakeup_src_y_mask = BIT(1), + .wakeup_src_x_mask = BIT(2), + }, }, { .wai = 0x6c, @@ -585,6 +651,14 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .mask = GENMASK(7, 4), }, }, + .lir = { + .addr = 0x56, + .mask = BIT(0), + }, + .clear_on_read = { + .addr = 0x56, + .mask = BIT(6), + }, .fifo_ops = { .update_fifo = st_lsm6dsx_update_fifo, .read_fifo = st_lsm6dsx_read_tagged_fifo, @@ -639,6 +713,9 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .wai = 0x6b, .int1_addr = 0x0d, .int2_addr = 0x0e, + .int1_func_addr = 0x5e, + .int2_func_addr = 0x5f, + .int_func_mask = BIT(5), .reset_addr = 0x12, .max_fifo_size = 512, .id = { @@ -715,6 +792,14 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .mask = GENMASK(7, 4), }, }, + .lir = { + .addr = 0x56, + .mask = BIT(0), + }, + .clear_on_read = { + .addr = 0x56, + .mask = BIT(6), + }, .fifo_ops = { .update_fifo = st_lsm6dsx_update_fifo, .read_fifo = st_lsm6dsx_read_tagged_fifo, @@ -738,11 +823,29 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .mask = GENMASK(7, 6), }, }, + .event_settings = { + .enable_reg = { + .addr = 0x58, + .mask = BIT(7), + }, + .wakeup_reg = { + .addr = 0x5B, + .mask = GENMASK(5, 0), + }, + .wakeup_src_reg = 0x1b, + .wakeup_src_status_mask = BIT(3), + .wakeup_src_z_mask = BIT(0), + .wakeup_src_y_mask = BIT(1), + .wakeup_src_x_mask = BIT(2), + }, }, { .wai = 0x6b, .int1_addr = 0x0d, .int2_addr = 0x0e, + .int1_func_addr = 0x5e, + .int2_func_addr = 0x5f, + .int_func_mask = BIT(5), .reset_addr = 0x12, .max_fifo_size = 512, .id = { @@ -822,6 +925,14 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .mask = GENMASK(7, 4), }, }, + .lir = { + .addr = 0x56, + .mask = BIT(0), + }, + .clear_on_read = { + .addr = 0x56, + .mask = BIT(6), + }, .fifo_ops = { .update_fifo = st_lsm6dsx_update_fifo, .read_fifo = st_lsm6dsx_read_tagged_fifo, @@ -870,6 +981,21 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .slv0_addr = 0x15, .dw_slv0_addr = 0x21, .batch_en = BIT(3), + }, + .event_settings = { + .enable_reg = { + .addr = 0x58, + .mask = BIT(7), + }, + .wakeup_reg = { + .addr = 0x5B, + .mask = GENMASK(5, 0), + }, + .wakeup_src_reg = 0x1b, + .wakeup_src_status_mask = BIT(3), + .wakeup_src_z_mask = BIT(0), + .wakeup_src_y_mask = BIT(1), + .wakeup_src_x_mask = BIT(2), } }, }; @@ -1076,7 +1202,8 @@ static int st_lsm6dsx_read_oneshot(struct st_lsm6dsx_sensor *sensor, if (err < 0) return err; - st_lsm6dsx_sensor_set_enable(sensor, false); + if (!hw->enable_event) + st_lsm6dsx_sensor_set_enable(sensor, false); *val = (s16)le16_to_cpu(data); @@ -1149,6 +1276,139 @@ static int st_lsm6dsx_write_raw(struct iio_dev *iio_dev, return err; } +static int st_lsm6dsx_event_setup(struct st_lsm6dsx_hw *hw, int state) +{ + int err; + u8 enable = 0; + + if (!hw->settings->int1_func_addr) + return -ENOTSUPP; + + enable = state ? hw->settings->event_settings.enable_reg.mask : 0; + + err = regmap_update_bits(hw->regmap, + hw->settings->event_settings.enable_reg.addr, + hw->settings->event_settings.enable_reg.mask, + enable); + if (err < 0) + return err; + + enable = state ? hw->irq_routing.mask : 0; + + /* Enable wakeup interrupt */ + return regmap_update_bits(hw->regmap, hw->irq_routing.addr, + hw->irq_routing.mask, + enable); +} + +static int st_lsm6dsx_read_event(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); + struct st_lsm6dsx_hw *hw = sensor->hw; + + if (type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + *val2 = 0; + *val = hw->event_threshold; + + return IIO_VAL_INT; +} + +static int st_lsm6dsx_write_event(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); + struct st_lsm6dsx_hw *hw = sensor->hw; + int err; + + if (type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + if (val < 0 || val > 31) + return -EINVAL; + + err = regmap_update_bits(hw->regmap, + hw->settings->event_settings.wakeup_reg.addr, + hw->settings->event_settings.wakeup_reg.mask, + val); + if (err) + return -EINVAL; + + hw->event_threshold = val; + + return 0; +} + +static int st_lsm6dsx_read_event_config(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); + struct st_lsm6dsx_hw *hw = sensor->hw; + + if (type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + return !!(hw->enable_event & BIT(chan->channel2)); +} + +static int st_lsm6dsx_write_event_config(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + int state) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); + struct st_lsm6dsx_hw *hw = sensor->hw; + u8 enable_event; + int err = 0; + + if (type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + if (state) { + enable_event = hw->enable_event | BIT(chan->channel2); + + /* do not enable events if they are already enabled */ + if (hw->enable_event) + goto out; + } else { + enable_event = hw->enable_event & ~BIT(chan->channel2); + + /* only turn off sensor if no events is enabled */ + if (enable_event) + goto out; + } + + /* stop here if no changes have been made */ + if (hw->enable_event == enable_event) + return 0; + + err = st_lsm6dsx_event_setup(hw, state); + if (err < 0) + return err; + + err = st_lsm6dsx_sensor_set_enable(sensor, state); + if (err < 0) + return err; + +out: + hw->enable_event = enable_event; + + return 0; +} + int st_lsm6dsx_set_watermark(struct iio_dev *iio_dev, unsigned int val) { struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); @@ -1233,6 +1493,10 @@ static const struct iio_info st_lsm6dsx_acc_info = { .attrs = &st_lsm6dsx_acc_attribute_group, .read_raw = st_lsm6dsx_read_raw, .write_raw = st_lsm6dsx_write_raw, + .read_event_value = st_lsm6dsx_read_event, + .write_event_value = st_lsm6dsx_write_event, + .read_event_config = st_lsm6dsx_read_event_config, + .write_event_config = st_lsm6dsx_write_event_config, .hwfifo_set_watermark = st_lsm6dsx_set_watermark, }; @@ -1278,9 +1542,13 @@ static int st_lsm6dsx_get_drdy_reg(struct st_lsm6dsx_hw *hw, u8 *drdy_reg) switch (drdy_pin) { case 1: *drdy_reg = hw->settings->int1_addr; + hw->irq_routing.addr = hw->settings->int1_func_addr; + hw->irq_routing.mask = hw->settings->int_func_mask; break; case 2: *drdy_reg = hw->settings->int2_addr; + hw->irq_routing.addr = hw->settings->int2_func_addr; + hw->irq_routing.mask = hw->settings->int_func_mask; break; default: dev_err(hw->dev, "unsupported data ready pin\n"); @@ -1416,6 +1684,29 @@ static int st_lsm6dsx_init_device(struct st_lsm6dsx_hw *hw) if (err < 0) return err; + /* enable Latched interrupts for device events */ + if (hw->settings->lir.addr) { + unsigned int data; + + data = ST_LSM6DSX_SHIFT_VAL(1, hw->settings->lir.mask); + err = regmap_update_bits(hw->regmap, hw->settings->lir.addr, + hw->settings->lir.mask, data); + if (err < 0) + return err; + + /* enable clear on read for latched interrupts */ + if (hw->settings->clear_on_read.addr) { + data = ST_LSM6DSX_SHIFT_VAL(1, + hw->settings->clear_on_read.mask); + err = regmap_update_bits(hw->regmap, + hw->settings->clear_on_read.addr, + hw->settings->clear_on_read.mask, + data); + if (err < 0) + return err; + } + } + err = st_lsm6dsx_init_shub(hw); if (err < 0) return err; @@ -1466,6 +1757,123 @@ static struct iio_dev *st_lsm6dsx_alloc_iiodev(struct st_lsm6dsx_hw *hw, return iio_dev; } +static void st_lsm6dsx_report_motion_event(struct st_lsm6dsx_hw *hw, int data) +{ + s64 timestamp = iio_get_time_ns(hw->iio_devs[ST_LSM6DSX_ID_ACC]); + + if ((data & hw->settings->event_settings.wakeup_src_z_mask) && + (hw->enable_event & BIT(IIO_MOD_Z))) + iio_push_event(hw->iio_devs[ST_LSM6DSX_ID_ACC], + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Z, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + timestamp); + + if ((data & hw->settings->event_settings.wakeup_src_y_mask) && + (hw->enable_event & BIT(IIO_MOD_Y))) + iio_push_event(hw->iio_devs[ST_LSM6DSX_ID_ACC], + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Y, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + timestamp); + + if ((data & hw->settings->event_settings.wakeup_src_x_mask) && + (hw->enable_event & BIT(IIO_MOD_X))) + iio_push_event(hw->iio_devs[ST_LSM6DSX_ID_ACC], + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_X, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + timestamp); +} + +static irqreturn_t st_lsm6dsx_handler_thread(int irq, void *private) +{ + struct st_lsm6dsx_hw *hw = private; + int count; + int data, err; + + if (hw->enable_event) { + err = regmap_read(hw->regmap, + hw->settings->event_settings.wakeup_src_reg, + &data); + if (err < 0) + return IRQ_NONE; + + if (data & hw->settings->event_settings.wakeup_src_status_mask) + st_lsm6dsx_report_motion_event(hw, data); + } + + mutex_lock(&hw->fifo_lock); + count = hw->settings->fifo_ops.read_fifo(hw); + mutex_unlock(&hw->fifo_lock); + + return count ? IRQ_HANDLED : IRQ_NONE; +} + +static int st_lsm6dsx_irq_setup(struct st_lsm6dsx_hw *hw) +{ + struct st_sensors_platform_data *pdata; + struct device_node *np = hw->dev->of_node; + unsigned long irq_type; + bool irq_active_low; + int err; + + irq_type = irqd_get_trigger_type(irq_get_irq_data(hw->irq)); + + switch (irq_type) { + case IRQF_TRIGGER_HIGH: + case IRQF_TRIGGER_RISING: + irq_active_low = false; + break; + case IRQF_TRIGGER_LOW: + case IRQF_TRIGGER_FALLING: + irq_active_low = true; + break; + default: + dev_info(hw->dev, "mode %lx unsupported\n", irq_type); + return -EINVAL; + } + + err = regmap_update_bits(hw->regmap, ST_LSM6DSX_REG_HLACTIVE_ADDR, + ST_LSM6DSX_REG_HLACTIVE_MASK, + FIELD_PREP(ST_LSM6DSX_REG_HLACTIVE_MASK, + irq_active_low)); + if (err < 0) + return err; + + pdata = (struct st_sensors_platform_data *)hw->dev->platform_data; + if ((np && of_property_read_bool(np, "drive-open-drain")) || + (pdata && pdata->open_drain)) { + err = regmap_update_bits(hw->regmap, ST_LSM6DSX_REG_PP_OD_ADDR, + ST_LSM6DSX_REG_PP_OD_MASK, + FIELD_PREP(ST_LSM6DSX_REG_PP_OD_MASK, + 1)); + if (err < 0) + return err; + + irq_type |= IRQF_SHARED; + } + + err = devm_request_threaded_irq(hw->dev, hw->irq, + NULL, + st_lsm6dsx_handler_thread, + irq_type | IRQF_ONESHOT, + "lsm6dsx", hw); + if (err) { + dev_err(hw->dev, "failed to request trigger irq %d\n", + hw->irq); + return err; + } + + return 0; +} + int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, struct regmap *regmap) { @@ -1514,6 +1922,10 @@ int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, } if (hw->irq > 0) { + err = st_lsm6dsx_irq_setup(hw); + if (err < 0) + return err; + err = st_lsm6dsx_fifo_setup(hw); if (err < 0) return err; @@ -1528,6 +1940,9 @@ int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, return err; } + if (dev->of_node && of_property_read_bool(dev->of_node, "wakeup-source")) + device_init_wakeup(dev, true); + return 0; } EXPORT_SYMBOL(st_lsm6dsx_probe); @@ -1546,6 +1961,13 @@ static int __maybe_unused st_lsm6dsx_suspend(struct device *dev) if (!(hw->enable_mask & BIT(sensor->id))) continue; + if (device_may_wakeup(dev) && + sensor->id == ST_LSM6DSX_ID_ACC && hw->enable_event) { + /* Enable wake from IRQ */ + enable_irq_wake(hw->irq); + continue; + } + if (sensor->id == ST_LSM6DSX_ID_EXT0 || sensor->id == ST_LSM6DSX_ID_EXT1 || sensor->id == ST_LSM6DSX_ID_EXT2) @@ -1575,6 +1997,10 @@ static int __maybe_unused st_lsm6dsx_resume(struct device *dev) continue; sensor = iio_priv(hw->iio_devs[i]); + if (device_may_wakeup(dev) && + sensor->id == ST_LSM6DSX_ID_ACC && hw->enable_event) + disable_irq_wake(hw->irq); + if (!(hw->suspend_mask & BIT(sensor->id))) continue; |