/* * da9052_tsi_filter.c -- TSI filter driver for Dialog DA9052 * * Copyright(c) 2009 Dialog Semiconductor Ltd. * * Author: Dialog Semiconductor Ltd * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * */ #include #include #include #define get_abs(x) (x < 0 ? ((-1) * x) : (x)) #define WITHIN_WINDOW(x, y) ((get_abs(x) <= TSI_X_WINDOW_SIZE) && \ (get_abs(y) <= TSI_Y_WINDOW_SIZE)) #define FIRST_SAMPLE 0 #define incr_with_wrap_raw_fifo(x) \ if (++x >= TSI_RAW_DATA_BUF_SIZE) \ x = 0 #define decr_with_wrap_raw_fifo(x) \ if (--x < 0) \ x = (TSI_RAW_DATA_BUF_SIZE-1) static u32 get_raw_data_cnt(struct da9052_ts_priv *priv); static void da9052_tsi_convert_reg_to_coord(struct da9052_ts_priv *priv, struct da9052_tsi_data *raw_data); static inline void clean_tsi_reg_fifo(struct da9052_ts_priv *priv); static inline void clean_tsi_raw_fifo(struct da9052_ts_priv *priv); #if (ENABLE_AVERAGE_FILTER) static void da9052_tsi_avrg_filter(struct da9052_ts_priv *priv, struct da9052_tsi_data *tsi_avg_data); #endif #if (ENABLE_TSI_DEBOUNCE) static s32 da9052_tsi_calc_debounce_data(struct da9052_ts_priv *priv, struct da9052_tsi_data *raw_data); #endif # if (ENABLE_WINDOW_FILTER) static s32 diff_within_window(struct da9052_tsi_data *prev_raw_data, struct da9052_tsi_data *cur_raw_data); #endif static s32 da9052_tsi_window_filter(struct da9052_ts_priv *ts, struct da9052_tsi_data *raw_data); void clean_tsi_fifos(struct da9052_ts_priv *priv) { clean_tsi_raw_fifo(priv); clean_tsi_reg_fifo(priv); } void __init da9052_init_tsi_fifos(struct da9052_ts_priv *priv) { sema_init(&priv->tsi_raw_fifo.lock, 1); sema_init(&priv->tsi_reg_fifo.lock, 1); clean_tsi_raw_fifo(priv); clean_tsi_reg_fifo(priv); } u32 get_reg_data_cnt(struct da9052_ts_priv *priv) { u8 reg_data_cnt; if (priv->tsi_reg_fifo.head <= priv->tsi_reg_fifo.tail) { reg_data_cnt = (priv->tsi_reg_fifo.tail - priv->tsi_reg_fifo.head); } else { reg_data_cnt = (priv->tsi_reg_fifo.tail + (TSI_REG_DATA_BUF_SIZE - priv->tsi_reg_fifo.head)); } return reg_data_cnt; } u32 get_reg_free_space_cnt(struct da9052_ts_priv *priv) { u32 free_cnt; if (priv->tsi_reg_fifo.head <= priv->tsi_reg_fifo.tail) { free_cnt = ((TSI_REG_DATA_BUF_SIZE - 1) - (priv->tsi_reg_fifo.tail - priv->tsi_reg_fifo.head)); } else free_cnt = ((priv->tsi_reg_fifo.head - priv->tsi_reg_fifo.tail) - 1); return free_cnt; } void da9052_tsi_process_reg_data(struct da9052_ts_priv *priv) { s32 ret; struct da9052_tsi_data tmp_raw_data; u32 reg_data_cnt; if (down_interruptible(&priv->tsi_reg_fifo.lock)) return; reg_data_cnt = get_reg_data_cnt(priv); while (reg_data_cnt-- > 0) { ret = 0; if (get_raw_data_cnt(priv) >= (TSI_RAW_DATA_BUF_SIZE - 1)) { DA9052_DEBUG("%s: RAW data FIFO is full\n", __FUNCTION__); break; } da9052_tsi_convert_reg_to_coord(priv, &tmp_raw_data); if ((tmp_raw_data.x < TS_X_MIN) || (tmp_raw_data.x > TS_X_MAX) || (tmp_raw_data.y < TS_Y_MIN) || (tmp_raw_data.y > TS_Y_MAX)) { DA9052_DEBUG("%s: ", __FUNCTION__); DA9052_DEBUG("sample beyond touchscreen panel "); DA9052_DEBUG("dimensions\n"); continue; } #if (ENABLE_TSI_DEBOUNCE) if (debounce_over == FALSE) { ret = da9052_tsi_calc_debounce_data(priv, &tmp_raw_data); if (ret != SUCCESS) continue; } #endif # if (ENABLE_WINDOW_FILTER) ret = da9052_tsi_window_filter(priv, &tmp_raw_data); if (ret != SUCCESS) continue; #endif priv->early_data_flag = FALSE; if (down_interruptible(&priv->tsi_raw_fifo.lock)) { DA9052_DEBUG("%s: Failed to ", __FUNCTION__); DA9052_DEBUG("acquire RAW FIFO Lock!\n"); up(&priv->tsi_reg_fifo.lock); return; } priv->tsi_raw_fifo.data[priv->tsi_raw_fifo.tail] = tmp_raw_data; incr_with_wrap_raw_fifo(priv->tsi_raw_fifo.tail); up(&priv->tsi_raw_fifo.lock); } up(&priv->tsi_reg_fifo.lock); return; } static u32 get_raw_data_cnt(struct da9052_ts_priv *priv) { u32 raw_data_cnt; if (priv->tsi_raw_fifo.head <= priv->tsi_raw_fifo.tail) raw_data_cnt = (priv->tsi_raw_fifo.tail - priv->tsi_raw_fifo.head); else raw_data_cnt = (priv->tsi_raw_fifo.tail + (TSI_RAW_DATA_BUF_SIZE - priv->tsi_raw_fifo.head)); return raw_data_cnt; } static void da9052_tsi_convert_reg_to_coord(struct da9052_ts_priv *priv, struct da9052_tsi_data *raw_data) { struct da9052_tsi_reg *src; struct da9052_tsi_data *dst = raw_data; src = &priv->tsi_reg_fifo.data[priv->tsi_reg_fifo.head]; dst->x = (src->x_msb << X_MSB_SHIFT); dst->x |= (src->lsb & X_LSB_MASK) >> X_LSB_SHIFT; dst->y = (src->y_msb << Y_MSB_SHIFT); dst->y |= (src->lsb & Y_LSB_MASK) >> Y_LSB_SHIFT; dst->z = (src->z_msb << Z_MSB_SHIFT); dst->z |= (src->lsb & Z_LSB_MASK) >> Z_LSB_SHIFT; #if DA9052_TSI_RAW_DATA_PROFILING printk("R\tX\t%4d\tY\t%4d\tZ\t%4d\n", (u16)dst->x, (u16)dst->y, (u16)dst->z); #endif priv->raw_data_cnt++; incr_with_wrap_reg_fifo(priv->tsi_reg_fifo.head); } #if (ENABLE_AVERAGE_FILTER) static void da9052_tsi_avrg_filter(struct da9052_ts_priv *priv, struct da9052_tsi_data *tsi_avg_data) { u8 cnt; if (down_interruptible(&priv->tsi_raw_fifo.lock)) { printk("%s: No RAW Lock !\n", __FUNCTION__); return; } (*tsi_avg_data) = priv->tsi_raw_fifo.data[priv->tsi_raw_fifo.head]; incr_with_wrap_raw_fifo(priv->tsi_raw_fifo.head); for (cnt = 2; cnt <= TSI_AVERAGE_FILTER_SIZE; cnt++) { tsi_avg_data->x += priv->tsi_raw_fifo.data[priv->tsi_raw_fifo.head].x; tsi_avg_data->y += priv->tsi_raw_fifo.data[priv->tsi_raw_fifo.head].y; tsi_avg_data->z += priv->tsi_raw_fifo.data[priv->tsi_raw_fifo.head].z; incr_with_wrap_raw_fifo(priv->tsi_raw_fifo.head); } up(&priv->tsi_raw_fifo.lock); tsi_avg_data->x /= TSI_AVERAGE_FILTER_SIZE; tsi_avg_data->y /= TSI_AVERAGE_FILTER_SIZE; tsi_avg_data->z /= TSI_AVERAGE_FILTER_SIZE; #if DA9052_TSI_AVG_FLT_DATA_PROFILING printk("A\tX\t%4d\tY\t%4d\tZ\t%4d\n", (u16)tsi_avg_data->x, (u16)tsi_avg_data->y, (u16)tsi_avg_data->z); #endif return; } #endif s32 da9052_tsi_raw_proc_thread(void *ptr) { struct da9052_tsi_data coord; u8 calib_ok, range_ok; struct calib_cfg_t *tsi_calib = get_calib_config(); struct input_dev *ip_dev = (struct input_dev *) da9052_tsi_get_input_dev( (u8)TSI_INPUT_DEVICE_OFF); struct da9052_ts_priv *priv = (struct da9052_ts_priv *)ptr; set_freezable(); while (priv->tsi_raw_proc_thread.state == ACTIVE) { try_to_freeze(); set_current_state(TASK_INTERRUPTIBLE); schedule_timeout( msecs_to_jiffies(priv->tsi_raw_data_poll_interval)); if (priv->early_data_flag || (get_raw_data_cnt(priv) == 0)) continue; calib_ok = TRUE; range_ok = TRUE; #if (ENABLE_AVERAGE_FILTER) if (get_raw_data_cnt(priv) < TSI_AVERAGE_FILTER_SIZE) continue; da9052_tsi_avrg_filter(priv, &coord); #else if (down_interruptible(&priv->tsi_raw_fifo.lock)) continue; coord = priv->tsi_raw_fifo.data[priv->tsi_raw_fifo.head]; incr_with_wrap_raw_fifo(priv->tsi_raw_fifo.head); up(&priv->tsi_raw_fifo.lock); #endif if (tsi_calib->calibrate_flag) { calib_ok = da9052_tsi_get_calib_display_point(&coord); if ((coord.x < DISPLAY_X_MIN) || (coord.x > DISPLAY_X_MAX) || (coord.y < DISPLAY_Y_MIN) || (coord.y > DISPLAY_Y_MAX)) range_ok = FALSE; } if (calib_ok && range_ok) { input_report_abs(ip_dev, BTN_TOUCH, 1); input_report_abs(ip_dev, ABS_X, coord.x); input_report_abs(ip_dev, ABS_Y, coord.y); input_sync(ip_dev); priv->os_data_cnt++; #if DA9052_TSI_OS_DATA_PROFILING printk("O\tX\t%4d\tY\t%4d\tZ\t%4d\n", (u16)coord.x, (u16)coord.y, (u16)coord.z); #endif } else { if (!calib_ok) { DA9052_DEBUG("%s: ", __FUNCTION__); DA9052_DEBUG("calibration Failed\n"); } if (!range_ok) { DA9052_DEBUG("%s: ", __FUNCTION__); DA9052_DEBUG("sample beyond display "); DA9052_DEBUG("panel dimension\n"); } } } priv->tsi_raw_proc_thread.thread_task = NULL; complete_and_exit(&priv->tsi_raw_proc_thread.notifier, 0); return 0; } #if (ENABLE_TSI_DEBOUNCE) static s32 da9052_tsi_calc_debounce_data(struct da9052_tsi_data *raw_data) { #if (TSI_DEBOUNCE_DATA_CNT) u8 cnt; struct da9052_tsi_data temp = {.x = 0, .y = 0, .z = 0}; priv->tsi_raw_fifo.data[priv->tsi_raw_fifo.tail] = (*raw_data); incr_with_wrap_raw_fifo(priv->tsi_raw_fifo.tail); if (get_raw_data_cnt(priv) <= TSI_DEBOUNCE_DATA_CNT) return -FAILURE; for (cnt = 1; cnt <= TSI_DEBOUNCE_DATA_CNT; cnt++) { temp.x += priv->tsi_raw_fifo.data[priv->tsi_raw_fifo.head].x; temp.y += priv->tsi_raw_fifo.data[priv->tsi_raw_fifo.head].y; temp.z += priv->tsi_raw_fifo.data[priv->tsi_raw_fifo.head].z; incr_with_wrap_raw_fifo(priv->tsi_raw_fifo.head); } temp.x /= TSI_DEBOUNCE_DATA_CNT; temp.y /= TSI_DEBOUNCE_DATA_CNT; temp.z /= TSI_DEBOUNCE_DATA_CNT; priv->tsi_raw_fifo.tail = priv->tsi_raw_fifo.head; priv->tsi_raw_fifo.data[priv->tsi_raw_fifo.tail] = temp; incr_with_wrap_raw_fifo(priv->tsi_raw_fifo.tail); #if DA9052_TSI_PRINT_DEBOUNCED_DATA printk("D: X: %d Y: %d Z: %d\n", (u16)temp.x, (u16)temp.y, (u16)temp.z); #endif #endif priv->debounce_over = TRUE; return 0; } #endif # if (ENABLE_WINDOW_FILTER) static s32 diff_within_window(struct da9052_tsi_data *prev_raw_data, struct da9052_tsi_data *cur_raw_data) { s32 ret = -EINVAL; s32 x, y; x = ((cur_raw_data->x) - (prev_raw_data->x)); y = ((cur_raw_data->y) - (prev_raw_data->y)); if (WITHIN_WINDOW(x, y)) { #if DA9052_TSI_WIN_FLT_DATA_PROFILING printk("W\tX\t%4d\tY\t%4d\tZ\t%4d\n", (u16)cur_raw_data->x, (u16)cur_raw_data->y, (u16)cur_raw_data->z); #endif ret = 0; } return ret; } static s32 da9052_tsi_window_filter(struct da9052_ts_priv *priv, struct da9052_tsi_data *raw_data) { u8 ref_found; u32 cur, next; s32 ret = -EINVAL; static struct da9052_tsi_data prev_raw_data; if (priv->win_reference_valid == TRUE) { #if DA9052_TSI_PRINT_PREVIOUS_DATA printk("P: X: %d Y: %d Z: %d\n", (u16)prev_raw_data.x, (u16)prev_raw_data.y, (u16)prev_raw_data.z); #endif ret = diff_within_window(&prev_raw_data, raw_data); if (!ret) prev_raw_data = (*raw_data); } else { priv->tsi_raw_fifo.data[priv->tsi_raw_fifo.tail] = (*raw_data); incr_with_wrap_raw_fifo(priv->tsi_raw_fifo.tail); if (get_raw_data_cnt(priv) == SAMPLE_CNT_FOR_WIN_REF) { ref_found = FALSE; next = cur = priv->tsi_raw_fifo.head; incr_with_wrap_raw_fifo(next); while (next <= priv->tsi_raw_fifo.tail) { ret = diff_within_window( &priv->tsi_raw_fifo.data[cur], &priv->tsi_raw_fifo.data[next] ); if (ret == SUCCESS) { ref_found = TRUE; break; } incr_with_wrap_raw_fifo(cur); incr_with_wrap_raw_fifo(next); } if (ref_found == FALSE) priv->tsi_raw_fifo.tail = priv->tsi_raw_fifo.head; else { prev_raw_data = priv->tsi_raw_fifo.data[cur]; prev_raw_data.x += priv->tsi_raw_fifo.data[next].x; prev_raw_data.y += priv->tsi_raw_fifo.data[next].y; prev_raw_data.z += priv->tsi_raw_fifo.data[next].z; prev_raw_data.x = prev_raw_data.x / 2; prev_raw_data.y = prev_raw_data.y / 2; prev_raw_data.z = prev_raw_data.z / 2; (*raw_data) = prev_raw_data; priv->tsi_raw_fifo.tail = priv->tsi_raw_fifo.head; priv->win_reference_valid = TRUE; ret = SUCCESS; } } } return ret; } #endif static inline void clean_tsi_reg_fifo(struct da9052_ts_priv *priv) { priv->tsi_reg_fifo.head = FIRST_SAMPLE; priv->tsi_reg_fifo.tail = FIRST_SAMPLE; } static inline void clean_tsi_raw_fifo(struct da9052_ts_priv *priv) { priv->tsi_raw_fifo.head = FIRST_SAMPLE; priv->tsi_raw_fifo.tail = FIRST_SAMPLE; }