/* * STMicroelectronics lsm6ds3 trigger driver * * Copyright 2014 STMicroelectronics Inc. * * Denis Ciocca * * Licensed under the GPL-2. */ #define DEBUG //TODO: remove #include #include #include #include #include #include #include #include #include #include #include #include "st_lsm6ds3.h" #define ST_LSM6DS3_SRC_FUNC_ADDR 0x53 #define ST_LSM6DS3_FIFO_DATA_AVL_ADDR 0x3b #define ST_LSM6DS3_SRC_STEP_DETECTOR_DATA_AVL 0x10 #define ST_LSM6DS3_SRC_TILT_DATA_AVL 0x20 #define ST_LSM6DS3_SRC_STEP_COUNTER_DATA_AVL 0x80 #define ST_LSM6DS3_FIFO_DATA_AVL 0x80 #define ST_LSM6DS3_FIFO_DATA_OVR 0x40 #define ST_LSM6DS3_TAP_SRC_ADDR 0x1c #define ST_LSM6DS3_TAP_SRC_DETECTED_MASK (1<<6) #define ST_LSM6DS3_TAP_SRC_SINGLE_TAP_MASK (1<<5) #define ST_LSM6DS3_TAP_SRC_Z_AXIS_MASK (1<<0) #define ST_LSM6DS3_6D_SRC_ADDR 0x1d #define WAKE_LOG_EVENTS 8 //must be power of 2 for kfifo static DECLARE_KFIFO(wake_time_fifo, s64, WAKE_LOG_EVENTS); #define HYPERACTIVITY_WINDOW_NS 5000000000L static struct workqueue_struct *st_lsm6ds3_wq; void st_lsm6ds3_flush_works() { flush_workqueue(st_lsm6ds3_wq); } int hyperactivity_check(struct lsm6ds3_data *cdata) { int i, fifo_len; int recent_count = 0; struct timespec ts; s64 ctime; s64 wtime[WAKE_LOG_EVENTS] = {0}; get_monotonic_boottime(&ts); ctime = timespec_to_ns(&ts); fifo_len = kfifo_len(&wake_time_fifo); dev_dbg(cdata->dev, "HYP_CHECK FIFO_LEN: %i/%i, time:%lld", fifo_len, kfifo_size(&wake_time_fifo), ctime); if(fifo_len < WAKE_LOG_EVENTS) return 0; kfifo_out(&wake_time_fifo, wtime, WAKE_LOG_EVENTS); for(i=0; i < WAKE_LOG_EVENTS; i++){ dev_dbg(cdata->dev, "wtime[%i]:%lld", i, wtime[i]); if(ctime - wtime[i] < HYPERACTIVITY_WINDOW_NS){ recent_count++; kfifo_in(&wake_time_fifo, &wtime[i], 1); } } dev_dbg(cdata->dev, "recent count:%i", recent_count); return (recent_count == WAKE_LOG_EVENTS); } irqreturn_t st_lsm6ds3_save_timestamp(int irq, void *private) { struct timespec ts; struct lsm6ds3_data *cdata = private; get_monotonic_boottime(&ts); cdata->timestamp = timespec_to_ns(&ts); cdata->accel_timestamp = cdata->timestamp; queue_work(st_lsm6ds3_wq, &cdata->data_work); disable_irq_nosync(irq); return IRQ_HANDLED; } static void st_lsm6ds3_irq_management(struct work_struct *data_work) { struct lsm6ds3_data *cdata; u8 d6d_src_reg, tap_src_reg; u8 src_value = 0x00, src_fifo = 0x00; u8 d6d_event = 0; u8 tap_event = 0; int woken_from_sleep = 0; int hyperactive = 0; cdata = container_of((struct work_struct *)data_work, struct lsm6ds3_data, data_work); if(cdata->first_irq_from_resume && last_wakeup_reason_test(cdata->irq)){ //take_wake_lock to give app enough time from wakeup woken_from_sleep = 1; wake_lock_timeout(&cdata->wlock,msecs_to_jiffies(1000)); } mutex_lock(&cdata->fifo_lock); cdata->tf->read(cdata, ST_LSM6DS3_6D_SRC_ADDR, 1, &d6d_src_reg, true); cdata->tf->read(cdata, ST_LSM6DS3_TAP_SRC_ADDR, 1, &tap_src_reg, true); cdata->tf->read(cdata, ST_LSM6DS3_SRC_FUNC_ADDR, 1, &src_value, true); cdata->tf->read(cdata, ST_LSM6DS3_FIFO_DATA_AVL_ADDR, 1, &src_fifo, true); dev_dbg(cdata->dev, "ST irq start :src_value, 6d, tap:%x %x %x",src_value, d6d_src_reg, tap_src_reg); if(d6d_src_reg & (1<<6)){ dev_info(cdata->dev, "D6D IRQ"); if(cdata->sixd_mask & d6d_src_reg){ d6d_event = 1; cdata->last_wakeup_source |= LSM6DS3_WAKEUP_6D; } else{ dev_info(cdata->dev, "ignoring 6d interrupt, wrong axis. mask: 0x%x", cdata->sixd_mask); } } if(tap_src_reg & ST_LSM6DS3_TAP_SRC_DETECTED_MASK){ dev_info(cdata->dev, "TAP IRQ"); if(tap_src_reg & (ST_LSM6DS3_TAP_SRC_SINGLE_TAP_MASK | ST_LSM6DS3_TAP_SRC_Z_AXIS_MASK)){ tap_event = 1; cdata->last_wakeup_source |= LSM6DS3_WAKEUP_TAP; dev_info(cdata->dev, "Valid Tap"); } else { dev_info(cdata->dev, "Ignoring tap"); } } if(woken_from_sleep){ dev_info(cdata->dev, "First IRQ from a RESUME"); if(!d6d_event && !tap_event){ dev_info(cdata->dev, "No event from first resume, assuming lost TAP"); tap_event = 1; cdata->last_wakeup_source |= LSM6DS3_WAKEUP_TAP; dev_info(cdata->dev, "Valid Tap from sleep"); } } if (src_fifo & ST_LSM6DS3_FIFO_DATA_AVL) { if(cdata->first_irq_from_resume){ st_lsm6ds3_read_fifo(cdata, true, true); } else{ st_lsm6ds3_read_fifo(cdata, true, true); } } //significant motion event processing if(!cdata->inactive_wait && (d6d_event || tap_event)){ kfifo_in(&wake_time_fifo, &cdata->timestamp, 1); hyperactive = hyperactivity_check(cdata); dev_info(cdata->dev, "Sending sig mot event; ready:%i",cdata->sign_motion_event_ready); wake_lock_timeout(&cdata->tap_wlock,msecs_to_jiffies(1500)); iio_push_event(cdata->indio_dev[ ST_INDIO_DEV_SIGN_MOTION], IIO_UNMOD_EVENT_CODE(IIO_SIGN_MOTION, 0, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER), cdata->accel_timestamp); cdata->sign_motion_event_ready = false; } if (src_value & ST_LSM6DS3_SRC_STEP_DETECTOR_DATA_AVL) { iio_push_event(cdata->indio_dev[ST_INDIO_DEV_STEP_DETECTOR], IIO_UNMOD_EVENT_CODE(IIO_STEP_DETECTOR, 0, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER), cdata->timestamp); if (cdata->sign_motion_event_ready) { dev_info(cdata->dev, "significant motion irq, pushing event (disable this?)"); iio_push_event(cdata->indio_dev[ ST_INDIO_DEV_SIGN_MOTION], IIO_UNMOD_EVENT_CODE(IIO_SIGN_MOTION, 0, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER), cdata->timestamp); cdata->sign_motion_event_ready = false; } } if (src_value & ST_LSM6DS3_SRC_STEP_COUNTER_DATA_AVL) { iio_trigger_poll_chained( cdata->trig[ST_INDIO_DEV_STEP_COUNTER], 0); } if (src_value & ST_LSM6DS3_SRC_TILT_DATA_AVL) { iio_push_event(cdata->indio_dev[ST_INDIO_DEV_TILT], IIO_UNMOD_EVENT_CODE(IIO_TILT, 0, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER), cdata->timestamp); } if(hyperactive){ uint8_t reg_value; int err; dev_info(cdata->dev, "Hyperactivity triggered, masking irqs except inactivity and fifo"); cdata->inactive_wait = 1; reg_value = 0x80;//fifo is in another register err = cdata->tf->write(cdata, 0x5e, 1, ®_value, true); st_lsm6ds3_set_inactive_detection(cdata, 1); } enable_irq(cdata->irq); mutex_unlock(&cdata->fifo_lock); cdata->first_irq_from_resume = 0; return; } int st_lsm6ds3_allocate_triggers(struct lsm6ds3_data *cdata, const struct iio_trigger_ops *trigger_ops) { int err, i, n; if (!st_lsm6ds3_wq) st_lsm6ds3_wq = create_workqueue(cdata->name); if (!st_lsm6ds3_wq) return -EINVAL; INIT_WORK(&cdata->data_work, st_lsm6ds3_irq_management); INIT_KFIFO(wake_time_fifo); for (i = 0; i < ST_INDIO_DEV_NUM; i++) { cdata->trig[i] = iio_trigger_alloc("%s-trigger", cdata->indio_dev[i]->name); if (!cdata->trig[i]) { dev_err(cdata->dev, "failed to allocate iio trigger.\n"); err = -ENOMEM; goto deallocate_trigger; } iio_trigger_set_drvdata(cdata->trig[i], cdata->indio_dev[i]); cdata->trig[i]->ops = trigger_ops; cdata->trig[i]->dev.parent = cdata->dev; } err = request_threaded_irq(cdata->irq, st_lsm6ds3_save_timestamp, NULL, IRQF_TRIGGER_RISING, cdata->name, cdata); if (err) goto deallocate_trigger; for (n = 0; n < ST_INDIO_DEV_NUM; n++) { err = iio_trigger_register(cdata->trig[n]); if (err < 0) { dev_err(cdata->dev, "failed to register iio trigger.\n"); goto free_irq; } cdata->indio_dev[n]->trig = cdata->trig[n]; } return 0; free_irq: free_irq(cdata->irq, cdata); for (n--; n >= 0; n--) iio_trigger_unregister(cdata->trig[n]); deallocate_trigger: for (i--; i >= 0; i--) iio_trigger_free(cdata->trig[i]); return err; } EXPORT_SYMBOL(st_lsm6ds3_allocate_triggers); void st_lsm6ds3_deallocate_triggers(struct lsm6ds3_data *cdata) { int i; free_irq(cdata->irq, cdata); for (i = 0; i < ST_INDIO_DEV_NUM; i++) iio_trigger_unregister(cdata->trig[i]); } EXPORT_SYMBOL(st_lsm6ds3_deallocate_triggers); MODULE_AUTHOR("Denis Ciocca "); MODULE_DESCRIPTION("STMicroelectronics lsm6ds3 trigger driver"); MODULE_LICENSE("GPL v2");