/* * STMicroelectronics lsm6ds3 trigger driver * * Copyright 2014 STMicroelectronics Inc. * * Denis Ciocca * * Licensed under the GPL-2. */ //#define WAKE_STATS_DEBUG_INFO //#define DEBUG #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 ST_LSM6DS3_6D_SRC_DETECTED_MASK (1<<6) static struct workqueue_struct *st_lsm6ds3_wq; void st_lsm6ds3_flush_works() { flush_workqueue(st_lsm6ds3_wq); } 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; } #ifdef WAKE_STATS_DEBUG_INFO static int wakeup_irq_count = 0; static int wakeup_irq_stayawake_count = 0; static int wakeup_irq_keepawake_count = 0; #define MAX_ORIENTATIONS 6 static int wake_up_orient_count[MAX_ORIENTATIONS] = {0}; static long wake_up_orient_times[MAX_ORIENTATIONS] = {0}; s64 last_orient_change_time_ns = 0; int last_orient; static void update_orient_stats(u8 d6d_src_reg, s64 ctime_ns) { return; s64 delta = 0; int orient_val; d6d_src_reg &= SIXD_MASK_VALID_BITS; orient_val = __ffs(d6d_src_reg); if( orient_val >= MAX_ORIENTATIONS){ return; } wake_up_orient_count[orient_val]++; if(last_orient_change_time_ns != 0){ delta = ctime_ns - last_orient_change_time_ns; do_div(delta, 1E6); //down to milliseconds wake_up_orient_times[last_orient] += delta; } last_orient_change_time_ns = ctime_ns; last_orient = orient_val; } static void print_wake_stats(struct lsm6ds3_data *cdata) { struct timespec monotonic, boot, misc; s64 mon_ns, boot_ns, misc_ns, tot_ms=0; int i; get_monotonic_boottime(&monotonic); ktime_get_ts(&boot); getnstimeofday(&misc); mon_ns = timespec_to_ns(&monotonic); boot_ns = timespec_to_ns(&boot); misc_ns = timespec_to_ns(&misc); do_div(mon_ns,1E9); do_div(boot_ns,1E9); do_div(misc_ns,1E9); dev_info(cdata->dev, "wkstat: unignored/total/keepawake: \t%i\t%i\t%i", wakeup_irq_stayawake_count, wakeup_irq_count, wakeup_irq_keepawake_count); dev_info(cdata->dev, "wkstat times: tot:\t%lld\tawake:\t%lld", mon_ns, boot_ns); dev_info(cdata->dev, "wkstat all orient count: 5-0 %i\t%i\t%i\t%i\t%i\t%i", wake_up_orient_count[5], wake_up_orient_count[4], wake_up_orient_count[3], wake_up_orient_count[2], wake_up_orient_count[1], wake_up_orient_count[0]); for(i=0; idev, "wkstat orient times(uS): 5-0 %ld\t%ld\t%ld\t%ld\t%ld\t%ld\ttot:%lld", wake_up_orient_times[5], wake_up_orient_times[4], wake_up_orient_times[3], wake_up_orient_times[2], wake_up_orient_times[1], wake_up_orient_times[0], tot_ms); } #endif /* WAKE_STATS_DEBUG_INFO */ 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 wake_irq; int ignore_event = 0; cdata = container_of((struct work_struct *)data_work, struct lsm6ds3_data, data_work); wake_irq = last_wakeup_reason_test(cdata->irq); 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 & ST_LSM6DS3_6D_SRC_DETECTED_MASK){ #ifdef WAKE_STATS_DEBUG_INFO update_orient_stats(d6d_src_reg, cdata->timestamp); #endif dev_info(cdata->dev, "D6D IRQ val:0x%x mask:0x%x", SIXD_MASK_VALID_BITS & d6d_src_reg, cdata->sixd_mask); if(cdata->sixd_mask & d6d_src_reg){ d6d_event = 1; cdata->last_wakeup_source |= LSM6DS3_WAKEUP_6D; } else{ ignore_event = 1; 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(cdata->first_irq_from_resume && wake_irq){ #ifdef WAKE_STATS_DEBUG_INFO wakeup_irq_count++; #endif if(!d6d_event && !tap_event && !ignore_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(!ignore_event && (tap_event || d6d_event) && cdata->first_irq_from_resume){ wake_lock_timeout(&cdata->tap_wlock,msecs_to_jiffies(1000)); #ifdef WAKE_STATS_DEBUG_INFO wakeup_irq_stayawake_count++; #endif } else if(d6d_event && !ignore_event){//negative roll wake_lock_timeout(&cdata->tap_wlock,msecs_to_jiffies(200)); #ifdef WAKE_STATS_DEBUG_INFO wakeup_irq_keepawake_count++; #endif } //significant motion event processing if(tap_event){ dev_info(cdata->dev, "Sending sig mot event(tap); ready:%i",cdata->sign_motion_event_ready); st_lsm6ds3_push_tap_to_fifo(cdata); 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_fifo & ST_LSM6DS3_FIFO_DATA_AVL) { st_lsm6ds3_read_fifo(cdata, true, true); } 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); } mutex_unlock(&cdata->fifo_lock); if(cdata->first_irq_from_resume){ cdata->first_irq_from_resume = 0; st_lsm6ds3_reconfigure_fifo(cdata, false); #ifdef WAKE_STATS_DEBUG_INFO print_wake_stats(cdata); #endif } enable_irq(cdata->irq); 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); 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_HIGH, 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");