diff options
author | Roger Liao <rogerliao@google.com> | 2020-06-11 11:03:19 +0800 |
---|---|---|
committer | Roger Liao <rogerliao@google.com> | 2020-06-11 11:03:21 +0800 |
commit | 5bf0a2155a7968e6ee2eb93c2162598005f4700c (patch) | |
tree | 81362f411a98863972ed6f1434e7bf2d76a90d92 | |
parent | 3f9e26e5f9a67b62fd51bd0f5e16e410b3be86e4 (diff) | |
parent | 3cda844bcb2212629d34872fe5fc5fd0e6e8194f (diff) | |
download | fts_touch-5bf0a2155a7968e6ee2eb93c2162598005f4700c.tar.gz |
Merge branch 'android-msm-pixel-4.19' into android-msm-barbet-4.19
From build 6575781
Bug: 158714637
Signed-off-by: Roger Liao <rogerliao@google.com>
Change-Id: I37e2dad02953b229bbd637af7bc0df528da51504
-rw-r--r-- | Kbuild | 8 | ||||
-rw-r--r-- | Kconfig | 13 | ||||
-rw-r--r-- | Makefile | 8 | ||||
-rw-r--r-- | fts.c | 6300 | ||||
-rw-r--r-- | fts.h | 531 | ||||
-rw-r--r-- | fts_lib/ftsCompensation.c | 1123 | ||||
-rw-r--r-- | fts_lib/ftsCompensation.h | 181 | ||||
-rw-r--r-- | fts_lib/ftsCore.c | 1243 | ||||
-rw-r--r-- | fts_lib/ftsCore.h | 216 | ||||
-rw-r--r-- | fts_lib/ftsError.c | 349 | ||||
-rw-r--r-- | fts_lib/ftsError.h | 236 | ||||
-rw-r--r-- | fts_lib/ftsFlash.c | 1123 | ||||
-rw-r--r-- | fts_lib/ftsFlash.h | 140 | ||||
-rw-r--r-- | fts_lib/ftsFrame.c | 667 | ||||
-rw-r--r-- | fts_lib/ftsFrame.h | 118 | ||||
-rw-r--r-- | fts_lib/ftsGesture.c | 377 | ||||
-rw-r--r-- | fts_lib/ftsGesture.h | 47 | ||||
-rw-r--r-- | fts_lib/ftsHardware.h | 319 | ||||
-rw-r--r-- | fts_lib/ftsIO.c | 924 | ||||
-rw-r--r-- | fts_lib/ftsIO.h | 80 | ||||
-rw-r--r-- | fts_lib/ftsSoftware.h | 557 | ||||
-rw-r--r-- | fts_lib/ftsTest.c | 7770 | ||||
-rw-r--r-- | fts_lib/ftsTest.h | 500 | ||||
-rw-r--r-- | fts_lib/ftsTime.c | 84 | ||||
-rw-r--r-- | fts_lib/ftsTime.h | 74 | ||||
-rw-r--r-- | fts_lib/ftsTool.c | 798 | ||||
-rw-r--r-- | fts_lib/ftsTool.h | 58 | ||||
-rw-r--r-- | fts_limits.h | 2045 | ||||
-rw-r--r-- | fts_proc.c | 3744 |
29 files changed, 29633 insertions, 0 deletions
@@ -0,0 +1,8 @@ +# FTM5 support +obj-$(CONFIG_TOUCHSCREEN_FTS) += ftm5.o +ftm5-objs = fts.o fts_proc.o +ftm5-objs += \ + fts_lib/ftsCompensation.o fts_lib/ftsCore.o fts_lib/ftsError.o \ + fts_lib/ftsFrame.o fts_lib/ftsIO.o fts_lib/ftsTest.o fts_lib/ftsTime.o \ + fts_lib/ftsTool.o fts_lib/ftsFlash.o fts_lib/ftsGesture.o + @@ -0,0 +1,13 @@ +# +# STMicroelectronics TOUCH driver configuration +# + +config TOUCHSCREEN_FTS + tristate "STMicroelectronics multitouch touchscreen - FingerTipS (FTM5)" + depends on SPI + select TOUCHSCREEN_HEATMAP + help + Say Y here to enable STMicroelectronics (FTM5) touchscreen support. + If unsure, say N. + To compile this driver as a module, choose M here: the module + will be called ftm5. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c5fe17b --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build +M ?= $(shell pwd) + +KBUILD_OPTIONS += CONFIG_TOUCHSCREEN_FTS=m + +modules modules_install clean: + $(MAKE) -C $(KERNEL_SRC) M=$(M) $(KBUILD_OPTIONS) $(@) + @@ -0,0 +1,6300 @@ +/** + * fts.c + * + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * marco.cali@st.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THE PRESENT SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, FOR THE SOLE + * PURPOSE TO SUPPORT YOUR APPLICATION DEVELOPMENT. + * AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY DIRECT, + * INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM + * THE CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE + * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. + * + * THIS SOFTWARE IS SPECIFICALLY DESIGNED FOR EXCLUSIVE USE WITH ST PARTS. + */ + + +/*! + * \file fts.c + * \brief It is the main file which contains all the most important functions + * generally used by a device driver the driver + */ +#include <linux/device.h> + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/ctype.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/input/mt.h> +#include <linux/interrupt.h> +#include <linux/hrtimer.h> +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/spi/spi.h> +#include <linux/completion.h> +#include <linux/device.h> +#include <linux/of.h> + +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <linux/regulator/consumer.h> + +#ifdef KERNEL_ABOVE_2_6_38 +#include <linux/input/mt.h> +#endif + +#include <drm/drm_panel.h> +#include <video/display_timing.h> + + +#include "fts.h" +#include "fts_lib/ftsCompensation.h" +#include "fts_lib/ftsCore.h" +#include "fts_lib/ftsIO.h" +#include "fts_lib/ftsError.h" +#include "fts_lib/ftsFlash.h" +#include "fts_lib/ftsFrame.h" +#include "fts_lib/ftsGesture.h" +#include "fts_lib/ftsTest.h" +#include "fts_lib/ftsTime.h" +#include "fts_lib/ftsTool.h" + +/* Touch simulation MT slot */ +#define TOUCHSIM_SLOT_ID 0 +#define TOUCHSIM_TIMER_INTERVAL_NS 8333333 + +/* Switch GPIO values */ +#define FTS_SWITCH_GPIO_VALUE_AP_MASTER 0 +#define FTS_SWITCH_GPIO_VALUE_SLPI_MASTER 1 + +/** + * Event handler installer helpers + */ +#define event_id(_e) (EVT_ID_##_e >> 4) +#define handler_name(_h) fts_##_h##_event_handler + +#define install_handler(_i, _evt, _hnd) \ + do { \ + _i->event_dispatch_table[event_id(_evt)] = handler_name(_hnd); \ + } while (0) + + +/* Use decimal-formatted raw data */ +#define RAW_DATA_FORMAT_DEC + +#ifdef KERNEL_ABOVE_2_6_38 +#define TYPE_B_PROTOCOL +#endif + + +extern SysInfo systemInfo; +extern TestToDo tests; +#ifdef GESTURE_MODE +extern struct mutex gestureMask_mutex; +#endif + +char fts_ts_phys[64]; /* /< buffer which store the input device name + * assigned by the kernel */ + +static u32 typeOfCommand[CMD_STR_LEN] = { 0 }; /* /< buffer used to store the + * command sent from the MP + * device file node */ +static int numberParameters; /* /< number of parameter passed through the MP + * device file node */ +#ifdef USE_ONE_FILE_NODE +static int feature_feasibility = ERROR_OP_NOT_ALLOW; +#endif +#ifdef GESTURE_MODE +static u8 mask[GESTURE_MASK_SIZE + 2]; +extern u16 gesture_coordinates_x[GESTURE_MAX_COORDS_PAIRS_REPORT]; +extern u16 gesture_coordinates_y[GESTURE_MAX_COORDS_PAIRS_REPORT]; +extern int gesture_coords_reported; +extern struct mutex gestureMask_mutex; +#endif + +#ifdef PHONE_KEY +static u8 key_mask; /* /< store the last update of the key mask + * published by the IC */ +#endif + +static int fts_init_sensing(struct fts_ts_info *info); +static int fts_mode_handler(struct fts_ts_info *info, int force); +static void fts_pinctrl_setup(struct fts_ts_info *info, bool active); + +static int fts_chip_initialization(struct fts_ts_info *info, int init_type); +#ifdef SUPPORT_PROX_PALM +static ssize_t audio_status_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); +static ssize_t audio_status_show(struct device *dev, + struct device_attribute *attr, + char *buf); +static ssize_t prox_palm_status_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); +static ssize_t prox_palm_status_show(struct device *dev, + struct device_attribute *attr, + char *buf); +#endif + +/** + * Release all the touches in the linux input subsystem + * @param info pointer to fts_ts_info which contains info about device/hw setup + */ +void release_all_touches(struct fts_ts_info *info) +{ + unsigned int type = MT_TOOL_FINGER; + int i; + + for (i = 0; i < TOUCH_ID_MAX; i++) { +#ifdef STYLUS_MODE + if (test_bit(i, &info->stylus_id)) + type = MT_TOOL_PEN; + else + type = MT_TOOL_FINGER; +#endif + input_mt_slot(info->input_dev, i); + input_report_abs(info->input_dev, ABS_MT_PRESSURE, 0); + input_mt_report_slot_state(info->input_dev, type, 0); + input_report_abs(info->input_dev, ABS_MT_TRACKING_ID, -1); + } + input_report_key(info->input_dev, BTN_TOUCH, 0); + input_sync(info->input_dev); + info->touch_id = 0; + info->palm_touch_mask = 0; + info->grip_touch_mask = 0; +#ifdef STYLUS_MODE + info->stylus_id = 0; +#endif +} + + +/** + * @defgroup file_nodes Driver File Nodes + * Driver publish a series of file nodes used to provide several utilities + * to the host and give him access to different API. + * @{ + */ + +/** + * @defgroup device_file_nodes Device File Nodes + * @ingroup file_nodes + * Device File Nodes \n + * There are several file nodes that are associated to the device and which + * are designed to be used by the host to enable/disable features or trigger + * some system specific actions \n + * Usually their final path depend on the definition of device tree node of + * the IC (e.g /sys/devices/soc.0/f9928000.i2c/i2c-6/6-0049) + * @{ + */ +/***************************************** FW UPGGRADE + * ***************************************************/ + +/** + * File node function to Update firmware from shell \n + * echo path_to_fw X Y > fwupdate perform a fw update \n + * where: \n + * path_to_fw = file name or path of the the FW to burn, if "NULL" the default + * approach selected in the driver will be used\n + * X = 0/1 to force the FW update whichever fw_version and config_id; + * 0=perform a fw update only if the fw in the file is newer than the fw in the + * chip \n + * Y = 0/1 keep the initialization data; 0 = will erase the initialization data + * from flash, 1 = will keep the initialization data + * the string returned in the shell is made up as follow: \n + * { = start byte \n + * X1X2X3X4 = 4 bytes in HEX format which represent an error code (00000000 no + * error) \n + * } = end byte + */ +static ssize_t fts_fwupdate_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret, mode[2]; + char path[100 + 1]; /* extra byte to hold '\0'*/ + struct fts_ts_info *info = dev_get_drvdata(dev); + + /* default(if not specified by user) set force = 0 and keep_cx to 1 */ + mode[0] = 0; + mode[1] = 1; + + /* reading out firmware upgrade parameters */ + if (sscanf(buf, "%100s %d %d", path, &mode[0], &mode[1]) >= 1) { + pr_info("%s: file = %s, force = %d, keep_cx = %d\n", __func__, + path, mode[0], mode[1]); + fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, true); + + if (info->sensor_sleep) + ret = ERROR_BUS_WR; + else + ret = flashProcedure(path, mode[0], mode[1]); + + info->fwupdate_stat = ret; + + fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, false); + + if (ret == ERROR_BUS_WR) + pr_err("%s: bus is not accessible. ERROR %08X\n", + __func__, ret); + else if (ret < OK) + pr_err("%s Unable to upgrade firmware! ERROR %08X\n", + __func__, ret); + } else + pr_err("%s: Wrong number of parameters! ERROR %08X\n", + __func__, ERROR_OP_NOT_ALLOW); + return count; +} + +static ssize_t fts_fwupdate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fts_ts_info *info = dev_get_drvdata(dev); + + /* fwupdate_stat: ERROR code Returned by flashProcedure. */ + return scnprintf(buf, PAGE_SIZE, "{ %08X }\n", info->fwupdate_stat); +} + + +/***************************************** UTILITIES + * (current fw_ver/conf_id, active mode, file fw_ver/conf_id) + ***************************************************/ +/** + * File node to show on terminal external release version in Little Endian \n + * (first the less significant byte) \n + * cat appid show the external release version of the FW running in the IC + */ +static ssize_t fts_appid_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct fts_ts_info *info = dev_get_drvdata(dev); + int written = 0; + char temp[35]; + + written += scnprintf(buf + written, PAGE_SIZE - written, + "REL: %s\n", + printHex("", + systemInfo.u8_releaseInfo, + EXTERNAL_RELEASE_INFO_SIZE, + temp, + sizeof(temp))); + written += scnprintf(buf + written, PAGE_SIZE - written, + "FW: %04X\nCFG: %04X\nAFE: %02X\nProject: %04X\n", + systemInfo.u16_fwVer, systemInfo.u16_cfgVer, + systemInfo.u8_cfgAfeVer, + systemInfo.u16_cfgProjectId); + written += scnprintf(buf + written, PAGE_SIZE - written, + "FW file: %s\n", info->board->fw_name); + + written += scnprintf(buf + written, PAGE_SIZE - written, + "Extended display info: "); + if (!info->extinfo.is_read) + written += scnprintf(buf + written, PAGE_SIZE - written, + "[pending]"); + else if (info->extinfo.size == 0) + written += scnprintf(buf + written, PAGE_SIZE - written, + "[none]"); + else if (info->extinfo.size * 2 < PAGE_SIZE - written) { + bin2hex(buf + written, info->extinfo.data, info->extinfo.size); + written += info->extinfo.size * 2; + } + + written += scnprintf(buf + written, PAGE_SIZE - written, + "\nMPFlag: %02X\n", + systemInfo.u8_mpFlag); + + return written; +} + +/** + * File node to show on terminal the mode that is active on the IC \n + * cat mode_active to show the bitmask which indicate + * the modes/features which are running on the IC in a specific instant of time + * the string returned in the shell is made up as follow: \n + * { = start byte \n + * X1 = 1 byte in HEX format which represent the actual running scan mode + * (@link scan_opt Scan Mode Options @endlink) \n + * X2 = 1 byte in HEX format which represent the bitmask on which is running + * the actual scan mode \n + * X3X4 = 2 bytes in HEX format which represent a bitmask of the features that + * are enabled at this moment (@link feat_opt Feature Selection Options + * @endlink) \n + * } = end byte + * @see fts_mode_handler() + */ +static ssize_t fts_mode_active_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fts_ts_info *info = dev_get_drvdata(dev); + + pr_info("Current mode active = %08X\n", info->mode); + return scnprintf(buf, PAGE_SIZE, "{ %08X }\n", info->mode); +} + +/** + * File node to show the fw_ver and config_id of the FW file + * cat fw_file_test show on the kernel log external release + * of the FW stored in the fw file/header file + */ +static ssize_t fts_fw_test_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fts_ts_info *info = dev_get_drvdata(dev); + Firmware fw; + int ret; + char temp[100] = { 0 }; + + fw.data = NULL; + ret = readFwFile(info->board->fw_name, &fw, 0); + + if (ret < OK) + pr_err("Error during reading FW file! ERROR %08X\n", ret); + else + pr_info("%s, size = %d bytes\n", + printHex("EXT Release = ", + systemInfo.u8_releaseInfo, + EXTERNAL_RELEASE_INFO_SIZE, + temp, + sizeof(temp)), + fw.data_size); + kfree(fw.data); + return 0; +} + +static ssize_t fts_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fts_ts_info *info = dev_get_drvdata(dev); + u8 *dump = NULL; + int dumpSize = ERROR_DUMP_ROW_SIZE * ERROR_DUMP_COL_SIZE; + u8 reg; + int written = 0; + int res; + int i; + + if (fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, true) < 0) { + pr_err("%s: bus is not accessible.\n", __func__); + written += scnprintf(buf, PAGE_SIZE, + "Bus is not accessible.\n"); + goto exit; + } + + written += scnprintf(buf + written, PAGE_SIZE - written, + "Mode: 0x%08X\n", info->mode); + + res = fts_writeReadU8UX(FTS_CMD_HW_REG_R, ADDR_SIZE_HW_REG, ADDR_ICR, + ®, 1, DUMMY_HW_REG); + if (res < 0) + pr_err("%s: failed to read ICR.\n", __func__); + else + written += scnprintf(buf + written, PAGE_SIZE - written, + "ICR: 0x%02X\n", reg); + + dump = kzalloc(dumpSize, GFP_KERNEL); + if (!dump) { + written += strlcat(buf + written, "Buffer allocation failed!\n", + PAGE_SIZE - written); + goto exit; + } + + res = dumpErrorInfo(dump, ERROR_DUMP_ROW_SIZE * ERROR_DUMP_COL_SIZE); + if (res >= 0) { + written += strlcat(buf + written, "Error dump:", + PAGE_SIZE - written); + for (i = 0; i < dumpSize; i++) { + if (i % 8 == 0) + written += scnprintf(buf + written, + PAGE_SIZE - written, + "\n%02X: ", i); + written += scnprintf(buf + written, + PAGE_SIZE - written, + "%02X ", dump[i]); + } + written += strlcat(buf + written, "\n", PAGE_SIZE - written); + } + +exit: + kfree(dump); + fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, false); + return written; +} + +#if 0 +/** + * File node to obtain and show strength frame + * cat strength_frame to obtain strength data \n + * the string returned in the shell is made up as follow: \n + * { = start byte \n + * X1X2X3X4 = 4 bytes in HEX format which represent an error code (00000000 no + *error) \n + * **** if error code is all 0s **** \n + * FF = 1 byte in HEX format number of rows \n + * SS = 1 byte in HEX format number of columns \n + * N1, ... = the decimal value of each node separated by a coma \n + * ********************************* \n + * } = end byte + */ +static ssize_t fts_strength_frame_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + MutualSenseFrame frame; + int res, count, j, size = (6 * 2) + 1, index = 0; + char *all_strbuff = NULL; + /* char buff[CMD_STR_LEN] = {0}; */ + /* struct i2c_client *client = to_i2c_client(dev); */ + struct fts_ts_info *info = dev_get_drvdata(dev); + + frame.node_data = NULL; + + res = fts_enableInterrupt(false); + if (res < OK) + goto END; + + res = senseOn(); + if (res < OK) { + pr_err("%s: could not start scanning! ERROR %08X\n", + __func__, res); + goto END; + } + mdelay(WAIT_FOR_FRESH_FRAMES); + res = senseOff(); + if (res < OK) { + pr_err("%s: could not finish scanning! ERROR %08X\n", + __func__, res); + goto END; + } + + mdelay(WAIT_AFTER_SENSEOFF); + flushFIFO(); + + res = getMSFrame3(MS_STRENGTH, &frame); + if (res < OK) { + pr_err("%s: could not get the frame! ERROR %08X\n", + __func__, res); + goto END; + } else { + size += (res * 6); + pr_info("The frame size is %d words\n", res); + res = OK; + print_frame_short("MS Strength frame =", array1dTo2d_short( + frame.node_data, frame.node_data_size, + frame.header.sense_node), + frame.header.force_node, + frame.header.sense_node); + } + +END: + flushFIFO(); + release_all_touches(info); + fts_mode_handler(info, 1); + + all_strbuff = (char *)kzalloc(size * sizeof(char), GFP_KERNEL); + + if (all_strbuff != NULL) { + snprintf(&all_strbuff[index], 11, "{ %08X", res); + + index += 10; + + if (res >= OK) { + snprintf(&all_strbuff[index], 3, "%02X", + (u8)frame.header.force_node); + index += 2; + snprintf(&all_strbuff[index], 3, "%02X", + (u8)frame.header.sense_node); + + index += 2; + + for (j = 0; j < frame.node_data_size; j++) { + snprintf(&all_strbuff[index], 10, "%d,%n", + frame.node_data[j], &count); + index += count; + } + + kfree(frame.node_data); + } + + snprintf(&all_strbuff[index], 3, " }"); + index += 2; + + count = snprintf(buf, TSP_BUF_SIZE, "%s\n", all_strbuff); + kfree(all_strbuff); + } else + pr_err("%s: Unable to allocate all_strbuff! ERROR %08X\n", + __func__, ERROR_ALLOC); + + fts_enableInterrupt(true); + return count; +} +#endif + +/***************************************** FEATURES + ***************************************************/ + +/* TODO: edit this function according to the features policy to allow during + * the screen on/off, following is shown an example but check always with ST + * for more details */ +/** + * Check if there is any conflict in enable/disable a particular feature + * considering the features already enabled and running + * @param info pointer to fts_ts_info which contains info about the device + * and its hw setup + * @param feature code of the feature that want to be tested + * @return OK if is possible to enable/disable feature, ERROR_OP_NOT_ALLOW + * in case of any other conflict + */ +int check_feature_feasibility(struct fts_ts_info *info, unsigned int feature) +{ + int res = OK; + +/* Example based on the status of the screen and on the feature + * that is trying to enable */ + /*res=ERROR_OP_NOT_ALLOW; + * if(info->resume_bit ==0){ + * switch(feature){ + #ifdef GESTURE_MODE + * case FEAT_SEL_GESTURE: + * res = OK; + * break; + #endif + * default: + * pr_err("%s: Feature not allowed in this + * operating mode! ERROR %08X\n", __func__, res); + * break; + * + * } + * }else{ + * switch(feature){ + #ifdef GESTURE_MODE + * case FEAT_SEL_GESTURE: + #endif + * case FEAT__SEL_GLOVE: // glove mode can only activate + *during sense on + * res = OK; + * break; + * + * default: + * pr_err("%s: Feature not allowed in this + * operating mode! ERROR %08X\n", __func__, res); + * break; + * + * } + * }*/ + + +/* Example based only on the feature that is going to be activated */ + switch (feature) { + case FEAT_SEL_GESTURE: + if (info->cover_enabled == 1) { + res = ERROR_OP_NOT_ALLOW; + pr_err("%s: Feature not allowed when in Cover mode! ERROR %08X\n", + __func__, res); + /* for example here can be placed a code for disabling + * the cover mode when gesture is activated */ + } + break; + + case FEAT_SEL_GLOVE: + if (info->gesture_enabled == 1) { + res = ERROR_OP_NOT_ALLOW; + pr_err("%s: Feature not allowed when Gestures enabled! ERROR %08X\n", + __func__, res); + /* for example here can be placed a code for disabling + * the gesture mode when cover is activated + * (that means that cover mode has + * an higher priority on gesture mode) */ + } + break; + + default: + pr_info("%s: Feature Allowed!\n", __func__); + } + + return res; +} + +#ifdef USE_ONE_FILE_NODE +/** + * File node to enable some feature + * echo XX 00/01 > feature_enable to enable/disable XX + * (possible values @link feat_opt Feature Selection Options @endlink) feature + * cat feature_enable to show the result of enabling/disabling process + * echo 01/00 > feature_enable; cat feature_enable to perform + * both actions stated before in just one call \n + * the string returned in the shell is made up as follow: \n + * { = start byte \n + * X1X2X3X4 = 4 bytes in HEX format which represent an error code (00000000 = + * no error) \n + * } = end byte + */ +static ssize_t fts_feature_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct fts_ts_info *info = dev_get_drvdata(dev); + char *p = (char *)buf; + unsigned int temp, temp2; + int res = OK; + + if (fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, true) < 0) { + res = ERROR_BUS_WR; + pr_err("%s: bus is not accessible.", __func__); + fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, false); + return count; + } + + if ((count - 2 + 1) / 3 != 1) + pr_err("fts_feature_enable: Number of parameter wrong! %d > %d\n", + (count - 2 + 1) / 3, 1); + else { + if (sscanf(p, "%02X %02X ", &temp, &temp2) == 2) { + p += 3; + res = check_feature_feasibility(info, temp); + if (res >= OK) { + switch (temp) { + #ifdef GESTURE_MODE + case FEAT_SEL_GESTURE: + info->gesture_enabled = temp2; + pr_info("fts_feature_enable: Gesture Enabled = %d\n", + info->gesture_enabled); + break; + #endif + + #ifdef GLOVE_MODE + case FEAT_SEL_GLOVE: + info->glove_enabled = temp2; + pr_info("fts_feature_enable: Glove Enabled = %d\n", + info->glove_enabled); + break; + #endif + + #ifdef STYLUS_MODE + case FEAT_SEL_STYLUS: + info->stylus_enabled = temp2; + pr_info("fts_feature_enable: Stylus Enabled = %d\n", + info->stylus_enabled); + break; + #endif + + #ifdef COVER_MODE + case FEAT_SEL_COVER: + info->cover_enabled = temp2; + pr_info("fts_feature_enable: Cover Enabled = %d\n", + info->cover_enabled); + break; + #endif + + #ifdef CHARGER_MODE + case FEAT_SEL_CHARGER: + info->charger_enabled = temp2; + pr_info("fts_feature_enable: Charger Enabled = %d\n", + info->charger_enabled); + break; + #endif + + #ifdef GRIP_MODE + case FEAT_SEL_GRIP: + info->grip_enabled = temp2; + pr_info("fts_feature_enable: Grip Enabled = %d\n", + info->grip_enabled); + break; + #endif + + + + default: + pr_err("fts_feature_enable: Feature %08X not valid! ERROR %08X\n", + temp, ERROR_OP_NOT_ALLOW); + res = ERROR_OP_NOT_ALLOW; + } + feature_feasibility = res; + } + if (feature_feasibility >= OK) + feature_feasibility = fts_mode_handler(info, 1); + else + pr_err("%s: Call echo XX 00/01 > feature_enable with a correct feature value (XX)! ERROR %08X\n", + __func__, res); + } else + pr_err("%s: Error when reading with sscanf!\n", + __func__); + } + + fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, false); + return count; +} + + + +static ssize_t fts_feature_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int count = 0; + + if (feature_feasibility < OK) + pr_err("%s: Call before echo XX 00/01 > feature_enable with a correct feature value (XX)! ERROR %08X\n", + __func__, feature_feasibility); + + count += scnprintf(buf + count, + PAGE_SIZE - count, "{ %08X }\n", + feature_feasibility); + + feature_feasibility = ERROR_OP_NOT_ALLOW; + return count; +} + +#else + + +#ifdef GRIP_MODE +/** + * File node to set the grip mode + * echo 01/00 > grip_mode to enable/disable glove mode \n + * cat grip_mode to show the status of the grip_enabled switch \n + * echo 01/00 > grip_mode; cat grip_mode to enable/disable grip + *mode + * and see the switch status in just one call \n + * the string returned in the shell is made up as follow: \n + * { = start byte \n + * X1X2X3X4 = 4 bytes in HEX format which represent the value + * info->grip_enabled (1 = enabled; 0= disabled) \n + * } = end byte + */ +static ssize_t fts_grip_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int count = 0; + + struct fts_ts_info *info = dev_get_drvdata(dev); + + pr_info("%s: grip_enabled = %d\n", __func__, + info->grip_enabled); + + count += scnprintf(buf + count, + PAGE_SIZE - count, "{ %08X }\n", + info->grip_enabled); + + return count; +} + + +static ssize_t fts_grip_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + char *p = (char *)buf; + unsigned int temp; + int res; + struct fts_ts_info *info = dev_get_drvdata(dev); + + if (fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, true) < 0) { + res = ERROR_BUS_WR; + pr_err("%s: bus is not accessible.", __func__); + fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, false); + return count; + } + + /* in case of a different elaboration of the input, just modify + * this initial part of the code according to customer needs */ + if ((count + 1) / 3 != 1) + pr_err("%s: Number of bytes of parameter wrong! %zu != 1 byte\n", + __func__, (count + 1) / 3); + else { + if (sscanf(p, "%02X ", &temp) == 1) { + p += 3; + +/* standard code that should be always used when a feature is enabled! */ +/* first step : check if the wanted feature can be enabled */ +/* second step: call fts_mode_handler to actually enable it */ +/* NOTE: Disabling a feature is always allowed by default */ + res = check_feature_feasibility(info, FEAT_SEL_GRIP); + if (res >= OK || temp == FEAT_DISABLE) { + info->grip_enabled = temp; + res = fts_mode_handler(info, 1); + if (res < OK) + pr_err("%s: Error during fts_mode_handler! ERROR %08X\n", + __func__, res); + } + } else + pr_err("%s: Error when reading with sscanf!\n", + __func__); + } + + fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, false); + return count; +} +#endif + +#ifdef CHARGER_MODE +/** + * File node to set the glove mode + * echo XX/00 > charger_mode to value >0 to enable + * (possible values: @link charger_opt Charger Options @endlink), + * 00 to disable charger mode \n + * cat charger_mode to show the status of the charger_enabled switch \n + * echo 01/00 > charger_mode; cat charger_mode to enable/disable + * charger mode and see the switch status in just one call \n + * the string returned in the shell is made up as follow: \n + * { = start byte \n + * X1X2X3X4 = 4 bytes in HEX format which represent the value + * info->charger_enabled (>0 = enabled; 0= disabled) \n + * } = end byte + */ +static ssize_t fts_charger_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int count = 0; + struct fts_ts_info *info = dev_get_drvdata(dev); + + pr_info("%s: charger_enabled = %d\n", __func__, + info->charger_enabled); + + count += scnprintf(buf + count, + PAGE_SIZE - count, "{ %08X }\n", + info->charger_enabled); + return count; +} + + +static ssize_t fts_charger_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + char *p = (char *)buf; + unsigned int temp; + int res; + struct fts_ts_info *info = dev_get_drvdata(dev); + + if (fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, true) < 0) { + res = ERROR_BUS_WR; + pr_err("%s: bus is not accessible.\n", __func__); + fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, false); + return count; + } + +/* in case of a different elaboration of the input, just modify this + * initial part of the code according to customer needs */ + if ((count + 1) / 3 != 1) + pr_err("%s: Number of bytes of parameter wrong! %zu != 1 byte\n", + __func__, (count + 1) / 3); + else { + if (sscanf(p, "%02X ", &temp) == 1) { + p += 3; + +/** standard code that should be always used when a feature is enabled! + * first step : check if the wanted feature can be enabled + * second step: call fts_mode_handler to actually enable it + * NOTE: Disabling a feature is always allowed by default + */ + res = check_feature_feasibility(info, FEAT_SEL_CHARGER); + if (res >= OK || temp == FEAT_DISABLE) { + info->charger_enabled = temp; + res = fts_mode_handler(info, 1); + if (res < OK) + pr_err("%s: Error during fts_mode_handler! ERROR %08X\n", + __func__, res); + } + } else + pr_err("%s: Error when reading with sscanf!\n", + __func__); + + } + + fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, false); + return count; +} +#endif + +#ifdef GLOVE_MODE +/** + * File node to set the glove mode + * echo 01/00 > glove_mode to enable/disable glove mode \n + * cat glove_mode to show the status of the glove_enabled switch \n + * echo 01/00 > glove_mode; cat glove_mode to enable/disable glove mode and + * see the switch status in just one call \n + * the string returned in the shell is made up as follow: \n + * { = start byte \n + * X1X2X3X4 = 4 bytes in HEX format which represent the of value + * info->glove_enabled (1 = enabled; 0= disabled) \n + * } = end byte + */ +static ssize_t fts_glove_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int count = 0; + struct fts_ts_info *info = dev_get_drvdata(dev); + + pr_info("%s: glove_enabled = %d\n", __func__, info->glove_enabled); + + count += scnprintf(buf + count, + PAGE_SIZE - count, "{ %08X }\n", + info->glove_enabled); + + return count; +} + + +static ssize_t fts_glove_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + char *p = (char *)buf; + unsigned int temp; + int res; + struct fts_ts_info *info = dev_get_drvdata(dev); + + if (fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, true) < 0) { + res = ERROR_BUS_WR; + pr_err("%s: bus is not accessible.\n", __func__); + fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, false); + return count; + } + +/* in case of a different elaboration of the input, just modify this + * initial part of the code according to customer needs */ + if ((count + 1) / 3 != 1) + pr_err("%s: Number of bytes of parameter wrong! %zu != 1 byte\n", + __func__, (count + 1) / 3); + else { + if (sscanf(p, "%02X ", &temp) == 1) { + p += 3; + +/* standard code that should be always used when a feature is enabled! */ +/* first step : check if the wanted feature can be enabled */ +/* second step: call fts_mode_handler to actually enable it */ +/* NOTE: Disabling a feature is always allowed by default */ + res = check_feature_feasibility(info, FEAT_SEL_GLOVE); + if (res >= OK || temp == FEAT_DISABLE) { + info->glove_enabled = temp; + res = fts_mode_handler(info, 1); + if (res < OK) + pr_err("%s: Error during fts_mode_handler! ERROR %08X\n", + __func__, res); + } + } else + pr_err("%s: Error when reading with sscanf!\n", + __func__); + } + + fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, false); + return count; +} +#endif + + +#ifdef COVER_MODE +/* echo 01/00 > cover_mode to enable/disable cover mode */ +/* cat cover_mode to show the status of the cover_enabled switch + * (example output in the terminal = "AA00000001BB" if the switch is enabled) */ +/* echo 01/00 > cover_mode; cat cover_mode to enable/disable cover mode and + * see the switch status in just one call */ +/* NOTE: the cover can be handled also using a notifier, in this case the body + * of these functions should be copied in the notifier callback */ +/** + * File node to set the cover mode + * echo 01/00 > cover_mode to enable/disable cover mode \n + * cat cover_mode to show the status of the cover_enabled switch \n + * echo 01/00 > cover_mode; cat cover_mode to enable/disable cover mode + * and see the switch status in just one call \n + * the string returned in the shell is made up as follow: \n + * { = start byte \n + * X1X2X3X4 = 4 bytes in HEX format which is the value of info->cover_enabled + * (1 = enabled; 0= disabled)\n + * } = end byte \n + * NOTE: \n + * the cover can be handled also using a notifier, in this case the body of + * these functions should be copied in the notifier callback + */ +static ssize_t fts_cover_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int count = 0; + struct fts_ts_info *info = dev_get_drvdata(dev); + + pr_info("%s: cover_enabled = %d\n", __func__, info->cover_enabled); + + count += scnprintf(buf + count, + PAGE_SIZE - count, "{ %08X }\n", + info->cover_enabled); + + return count; +} + + +static ssize_t fts_cover_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + char *p = (char *)buf; + unsigned int temp; + int res; + struct fts_ts_info *info = dev_get_drvdata(dev); + + if (fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, true) < 0) { + res = ERROR_BUS_WR; + pr_err("%s: bus is not accessible.\n", __func__); + fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, false); + return count; + } + +/* in case of a different elaboration of the input, just modify this + * initial part of the code according to customer needs */ + if ((count + 1) / 3 != 1) + pr_err("%s: Number of bytes of parameter wrong! %zu != 1 byte\n", + __func__, (count + 1) / 3); + else { + if (sscanf(p, "%02X ", &temp) == 1) { + p += 3; + +/* standard code that should be always used when a feature is enabled! */ +/* first step : check if the wanted feature can be enabled */ +/* second step: call fts_mode_handler to actually enable it */ +/* NOTE: Disabling a feature is always allowed by default */ + res = check_feature_feasibility(info, FEAT_SEL_COVER); + if (res >= OK || temp == FEAT_DISABLE) { + info->cover_enabled = temp; + res = fts_mode_handler(info, 1); + if (res < OK) + pr_err("%s: Error during fts_mode_handler! ERROR %08X\n", + __func__, res); + } + } else + pr_err("%s: Error when reading with sscanf!\n", + __func__); + } + + fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, false); + return count; +} +#endif + +#ifdef STYLUS_MODE +/** + * File node to enable the stylus report + * echo 01/00 > stylus_mode to enable/disable stylus mode \n + * cat stylus_mode to show the status of the stylus_enabled switch \n + * echo 01/00 > stylus_mode; cat stylus_mode to enable/disable stylus mode + * and see the switch status in just one call \n + * the string returned in the shell is made up as follow: \n + * { = start byte \n + * X1X2X3X4 = 4 bytes in HEX format which is the value of info->stylus_enabled + * (1 = enabled; 0= disabled)\n + * } = end byte + */ +static ssize_t fts_stylus_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int count = 0; + struct fts_ts_info *info = dev_get_drvdata(dev); + + pr_info("%s: stylus_enabled = %d\n", __func__, info->stylus_enabled); + + count += scnprintf(buf + count, + PAGE_SIZE - count, "{ %08X }\n", + info->stylus_enabled); + + return count; +} + + +static ssize_t fts_stylus_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + char *p = (char *)buf; + unsigned int temp; + struct fts_ts_info *info = dev_get_drvdata(dev); + + +/* in case of a different elaboration of the input, just modify this + * initial part of the code according to customer needs */ + if ((count + 1) / 3 != 1) + pr_err("%s: Number of bytes of parameter wrong! %zu != 1 byte\n", + __func__, (count + 1) / 3); + else { + if (sscanf(p, "%02X ", &temp) == 1) { + p += 3; + info->stylus_enabled = temp; + } else + pr_err("%s: Error when reading with sscanf!\n", + __func__); + } + + return count; +} +#endif + +#endif + +/***************************************** GESTURES + ***************************************************/ +#ifdef GESTURE_MODE +#ifdef USE_GESTURE_MASK /* if this define is used, a gesture bit mask + * is used as method to select the gestures to + * enable/disable */ + +/** + * File node used by the host to set the gesture mask to enable or disable + * echo EE X1 X2 ~~ > gesture_mask set the gesture to disable/enable; + * EE = 00(disable) or 01(enable) \n + * X1 ~~ = gesture mask (example 06 00 ~~ 00 this gesture mask represents + * the gestures with ID = 1 and 2) can be specified + * from 1 to GESTURE_MASK_SIZE bytes, \n + * if less than GESTURE_MASK_SIZE bytes are passed as arguments, + * the omit bytes of the mask maintain the previous settings \n + * if one or more gestures is enabled the driver will automatically + * enable the gesture mode, If all the gestures are disabled the driver + * automatically will disable the gesture mode \n + * cat gesture_mask set inside the specified mask and return an error code + * for the operation \n + * the string returned in the shell is made up as follow: \n + * { = start byte \n + * X1X2X3X4 = 4 bytes in HEX format which represent an error code for enabling + * the mask (00000000 = no error)\n + * } = end byte \n\n + * if USE_GESTURE_MASK is not define the usage of the function become: \n\n + * echo EE X1 X2 ~~ > gesture_mask set the gesture to disable/enable; + * EE = 00(disable) or 01(enable) \n + * X1 ~~ = gesture IDs (example 01 02 05 represent the gestures with ID = 1, 2 + * and 5) + * there is no limit of the IDs passed as arguments, (@link gesture_opt Gesture + * IDs @endlink) \n + * if one or more gestures is enabled the driver will automatically enable + * the gesture mode. If all the gestures are disabled the driver automatically + * will disable the gesture mode. \n + * cat gesture_mask to show the status of the gesture enabled switch \n + * the string returned in the shell is made up as follow: \n + * { = start byte \n + * X1X2X3X4 = 4 bytes in HEX format which is the value of info->gesture_enabled + * (1 = enabled; 0= disabled)\n + * } = end byte + */ +static ssize_t fts_gesture_mask_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int count = 0, res, temp; + struct fts_ts_info *info = dev_get_drvdata(dev); + + if (fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, true) < 0) { + res = ERROR_BUS_WR; + pr_err("%s: bus is not accessible.\n", __func__); + scnprintf(buf, PAGE_SIZE, "{ %08X }\n", res); + fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, false); + return count; + } + + if (mask[0] == 0) { + res = ERROR_OP_NOT_ALLOW; + pr_err("%s: Call before echo enable/disable xx xx .... > gesture_mask with a correct number of parameters! ERROR %08X\n", + __func__, res); + } else { + if (mask[1] == FEAT_ENABLE || mask[1] == FEAT_DISABLE) + res = updateGestureMask(&mask[2], mask[0], mask[1]); + else + res = ERROR_OP_NOT_ALLOW; + + if (res < OK) + pr_err("fts_gesture_mask_store: ERROR %08X\n", res); + } + res |= check_feature_feasibility(info, FEAT_SEL_GESTURE); + temp = isAnyGestureActive(); + if (res >= OK || temp == FEAT_DISABLE) + info->gesture_enabled = temp; + + pr_info("fts_gesture_mask_store: Gesture Enabled = %d\n", + info->gesture_enabled); + + count += scnprintf(buf + count, + PAGE_SIZE - count, "{ %08X }\n", res); + mask[0] = 0; + + fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, false); + return count; +} + + +static ssize_t fts_gesture_mask_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + char *p = (char *)buf; + int n; + unsigned int temp; + + if ((count + 1) / 3 > GESTURE_MASK_SIZE + 1) { + pr_err("fts_gesture_mask_store: Number of bytes of parameter wrong! %zu > (enable/disable + %d )\n", + (count + 1) / 3, GESTURE_MASK_SIZE); + mask[0] = 0; + } else { + mask[0] = ((count + 1) / 3) - 1; + for (n = 1; n <= (count + 1) / 3; n++) { + if (sscanf(p, "%02X ", &temp) == 1) { + p += 3; + mask[n] = (u8)temp; + pr_info("mask[%d] = %02X\n", n, mask[n]); + } else + pr_err("%s: Error when reading with sscanf!\n", + __func__); + } + } + + return count; +} + +#else /* if this define is not used, to select the gestures to enable/disable + * are used the IDs of the gestures */ +/* echo EE X1 X2 ... > gesture_mask set the gesture to disable/enable; + * EE = 00(disable) or 01(enable); X1 ... = gesture IDs + * (example 01 02 05... represent the gestures with ID = 1, 2 and 5) + * there is no limit of the parameters that can be passed, + * of course the gesture IDs should be valid (all the valid IDs are listed in + * ftsGesture.h) */ +/* cat gesture_mask enable/disable the given gestures, if one or more + * gestures is enabled the driver will automatically enable the gesture mode. + * If all the gestures are disabled the driver automatically will disable the + * gesture mode. + * At the end an error code will be printed + * (example output in the terminal = "AA00000000BB" if there are no errors) */ +/* echo EE X1 X2 ... > gesture_mask; cat gesture_mask perform in one command + * both actions stated before */ +/** + * File node used by the host to set the gesture mask to enable or disable + * echo EE X1 X2 ~~ > gesture_mask set the gesture to disable/enable; + * EE = 00(disable) or 01(enable) \n + * X1 ~ = gesture IDs (example 01 02 05 represent the gestures with ID = 1, 2 + * and 5) + * there is no limit of the IDs passed as arguments, (@link gesture_opt Gesture + * IDs @endlink) \n + * if one or more gestures is enabled the driver will automatically enable + * the gesture mode, If all the gestures are disabled the driver automatically + * will disable the gesture mode \n + * cat gesture_mask to show the status of the gesture enabled switch \n + * the string returned in the shell is made up as follow: \n + * { = start byte \n + * X1X2X3X4 = 4 bytes in HEX format which is the value of info->gesture_enabled + * (1 = enabled; 0= disabled)\n + * } = end byte + */ +static ssize_t fts_gesture_mask_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int count = 0; + struct fts_ts_info *info = dev_get_drvdata(dev); + + pr_info("fts_gesture_mask_show: gesture_enabled = %d\n", + info->gesture_enabled); + + count += scnprintf(buf + count, + PAGE_SIZE - count, "{ %08X }\n", + info->gesture_enabled); + + + return count; +} + + +static ssize_t fts_gesture_mask_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + char *p = (char *)buf; + int n; + unsigned int temp; + int res; + struct fts_ts_info *info = dev_get_drvdata(dev); + + if (fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, true) < 0) { + res = ERROR_BUS_WR; + pr_err("%s: bus is not accessible.\n", __func__); + fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, false); + return count; + } + + if ((count + 1) / 3 < 2 || (count + 1) / 3 > GESTURE_MASK_SIZE + 1) { + pr_err("fts_gesture_mask_store: Number of bytes of parameter wrong! %d < or > (enable/disable + at least one gestureID or max %d bytes)\n", + (count + 1) / 3, GESTURE_MASK_SIZE); + mask[0] = 0; + } else { + memset(mask, 0, GESTURE_MASK_SIZE + 2); + mask[0] = ((count + 1) / 3) - 1; + if (sscanf(p, "%02X ", &temp) == 1) { + p += 3; + mask[1] = (u8)temp; + for (n = 1; n < (count + 1) / 3; n++) { + if (sscanf(p, "%02X ", &temp) == 1) { + p += 3; + fromIDtoMask((u8)temp, &mask[2], + GESTURE_MASK_SIZE); + } else { + pr_err("%s: Error when reading with sscanf!\n", + __func__); + mask[0] = 0; + goto END; + } + } + + for (n = 0; n < GESTURE_MASK_SIZE + 2; n++) + pr_info("mask[%d] = %02X\n", n, mask[n]); + } else { + pr_err("%s: Error when reading with sscanf!\n", + __func__); + mask[0] = 0; + } + } + +END: + if (mask[0] == 0) { + res = ERROR_OP_NOT_ALLOW; + pr_err("%s: Call before echo enable/disable xx xx .... > gesture_mask with a correct number of parameters! ERROR %08X\n", + __func__, res); + } else { + if (mask[1] == FEAT_ENABLE || mask[1] == FEAT_DISABLE) + res = updateGestureMask(&mask[2], mask[0], mask[1]); + else + res = ERROR_OP_NOT_ALLOW; + + if (res < OK) + pr_err("fts_gesture_mask_store: ERROR %08X\n", res); + } + + res = check_feature_feasibility(info, FEAT_SEL_GESTURE); + temp = isAnyGestureActive(); + if (res >= OK || temp == FEAT_DISABLE) + info->gesture_enabled = temp; + res = fts_mode_handler(info, 0); + + fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, false); + return count; +} + + +#endif + + +/** + * File node to read the coordinates of the last gesture drawn by the user \n + * cat gesture_coordinates to obtain the gesture coordinates \n + * the string returned in the shell follow this up as follow: \n + * { = start byte \n + * X1X2X3X4 = 4 bytes in HEX format which represent an error code (00000000 = + *OK) \n + * \n if error code = 00000000 \n + * CC = 1 byte in HEX format number of coords (pair of x,y) returned \n + * XXiYYi ... = XXi 2 bytes in HEX format for x[i] and + * YYi 2 bytes in HEX format for y[i] (big endian) \n + * \n + * } = end byte + */ +static ssize_t fts_gesture_coordinates_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int size = PAGE_SIZE; + int count = 0, res, i = 0; + + pr_info("%s: Getting gestures coordinates...\n", __func__); + + if (gesture_coords_reported < OK) { + pr_err("%s: invalid coordinates! ERROR %08X\n", + __func__, gesture_coords_reported); + res = gesture_coords_reported; + } else { + size += gesture_coords_reported * 2 * 4 + 2; + /* coords are pairs of x,y (*2) where each coord is + * short(2bytes=4char)(*4) + 1 byte(2char) num of coords (+2) + **/ + res = OK; /* set error code to OK */ + } + + + count += scnprintf(buf + count, + size - count, "{ %08X", res); + + if (res >= OK) { + count += scnprintf(buf + count, + size - count, "%02X", + gesture_coords_reported); + + for (i = 0; i < gesture_coords_reported; i++) { + count += scnprintf(buf + count, + size - count, + "%04X", + gesture_coordinates_x[i]); + count += scnprintf(buf + count, + size - count, + "%04X", + gesture_coordinates_y[i]); + } + } + + count += scnprintf(buf + count, size - count, " }\n"); + pr_info("%s: Getting gestures coordinates FINISHED!\n", __func__); + + return count; +} +#endif + +/* Touch simulation hr timer expiry callback */ +static enum hrtimer_restart touchsim_timer_cb(struct hrtimer *timer) +{ + struct fts_touchsim *touchsim = container_of(timer, + struct fts_touchsim, + hr_timer); + enum hrtimer_restart retval = HRTIMER_NORESTART; + + if (touchsim->is_running) { + hrtimer_forward_now(timer, + ns_to_ktime(TOUCHSIM_TIMER_INTERVAL_NS)); + retval = HRTIMER_RESTART; + } + + /* schedule the task to report touch coordinates to kernel + * input subsystem + */ + queue_work(touchsim->wq, &touchsim->work); + + return retval; +} + +/* Compute the next touch coordinate(x,y) */ +static void touchsim_refresh_coordinates(struct fts_touchsim *touchsim) +{ + struct fts_ts_info *info = container_of(touchsim, + struct fts_ts_info, + touchsim); + + const int x_start = info->board->x_axis_max / 10; + const int x_max = (info->board->x_axis_max * 9) / 10; + const int y_start = info->board->y_axis_max / 4; + const int y_max = info->board->y_axis_max / 2; + + touchsim->x += touchsim->x_step; + touchsim->y += touchsim->y_step; + + if (touchsim->x < x_start || touchsim->x > x_max) + touchsim->x_step *= -1; + + if (touchsim->y < y_start || touchsim->y > y_max) + touchsim->y_step *= -1; +} + +/* Report touch contact */ +static void touchsim_report_contact_event(struct input_dev *dev, int slot_id, + int x, int y, int z) +{ + /* report the cordinates to the input subsystem */ + input_mt_slot(dev, slot_id); + input_report_key(dev, BTN_TOUCH, true); + input_mt_report_slot_state(dev, MT_TOOL_FINGER, true); + input_report_abs(dev, ABS_MT_POSITION_X, x); + input_report_abs(dev, ABS_MT_POSITION_Y, y); + input_report_abs(dev, ABS_MT_PRESSURE, z); +} + +/* Work callback to report the touch co-ordinates to input subsystem */ +static void touchsim_work(struct work_struct *work) +{ + struct fts_touchsim *touchsim = container_of(work, + struct fts_touchsim, + work); + struct fts_ts_info *info = container_of(touchsim, + struct fts_ts_info, + touchsim); +#ifdef TOUCHSCREEN_HEATMAP + ktime_t timestamp = ktime_get(); +#endif + + /* prevent CPU from entering deep sleep */ + pm_qos_update_request(&info->pm_qos_req, 100); + + /* Notify the PM core that the wakeup event will take 1 sec */ + __pm_wakeup_event(info->wakesrc, jiffies_to_msecs(HZ)); + + /* get the next touch coordinates */ + touchsim_refresh_coordinates(touchsim); + + /* send the touch co-ordinates */ + touchsim_report_contact_event(info->input_dev, TOUCHSIM_SLOT_ID, + touchsim->x, touchsim->y, 1); + + input_sync(info->input_dev); + +#ifdef TOUCHSCREEN_HEATMAP + heatmap_read(&info->v4l2, ktime_to_ns(timestamp)); +#endif + + pm_qos_update_request(&info->pm_qos_req, PM_QOS_DEFAULT_VALUE); +} + +/* Start the touch simulation */ +static int touchsim_start(struct fts_touchsim *touchsim) +{ + struct fts_ts_info *info = container_of(touchsim, + struct fts_ts_info, + touchsim); + int res; + + if (!touchsim->wq) { + pr_err("%s: touch simulation test wq is not available!\n", + __func__); + return -EFAULT; + } + + if (touchsim->is_running) { + pr_err("%s: test in progress!\n", __func__); + return -EBUSY; + } + + /* setup the initial touch coordinates*/ + touchsim->x = info->board->x_axis_max / 10; + touchsim->y = info->board->y_axis_max / 4; + + touchsim->is_running = true; + + touchsim->x_step = 2; + touchsim->y_step = 2; + + /* Disable touch interrupts from hw */ + res = fts_enableInterrupt(false); + if ( res != OK) + pr_err("%s: fts_enableInterrupt: ERROR %08X\n", __func__, res); + + /* Release all touches in the linux input subsystem */ + release_all_touches(info); + + /* setup and start a hr timer to be fired every 120Hz(~8.333333ms) */ + hrtimer_init(&touchsim->hr_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); + touchsim->hr_timer.function = touchsim_timer_cb; + hrtimer_start(&touchsim->hr_timer, + ns_to_ktime(TOUCHSIM_TIMER_INTERVAL_NS), + HRTIMER_MODE_ABS); + + return OK; +} + +/* Stop the touch simulation test */ +static int touchsim_stop(struct fts_touchsim *touchsim) +{ + struct fts_ts_info *info = container_of(touchsim, + struct fts_ts_info, + touchsim); + int res; + + if (!touchsim->is_running) { + pr_err("%s: test is not in progress!\n", __func__); + return -EINVAL; + } + + /* Set the flag here to make sure flushed work doesn't + * re-start the timer + */ + touchsim->is_running = false; + + hrtimer_cancel(&touchsim->hr_timer); + + /* flush any pending work */ + flush_workqueue(touchsim->wq); + + /* Release all touches in the linux input subsystem */ + release_all_touches(info); + + /* re enable the hw touch interrupt */ + res = fts_enableInterrupt(true); + if ( res != OK) + pr_err("%s: fts_enableInterrupt: ERROR %08X\n", __func__, res); + + + return OK; +} + +/** sysfs file node to handle the touch simulation test request. + * "cat touchsim" shows if the test is running + * Possible outputs: + * 1 = test running. + * 0 = test not running. + */ +static ssize_t fts_touch_simulation_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fts_ts_info *info = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%u\n", + info->touchsim.is_running ? 1 : 0); +} + +/** sysfs file node to handle the touch simulation test request. + * "echo <cmd> > touchsim" to execute a command + * Possible commands (cmd): + * 1 = start the test if not already running. + * 0 = stop the test if its running. + */ +static ssize_t fts_touch_simulation_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct fts_ts_info *info = dev_get_drvdata(dev); + ssize_t retval = count; + u8 result; + + if (!mutex_trylock(&info->diag_cmd_lock)) { + pr_err("%s: Blocking concurrent access\n", __func__); + retval = -EBUSY; + goto out; + } + + if (kstrtou8(buf, 16, &result)) { + pr_err("%s:bad input. valid inputs are either 0 or 1!\n", + __func__); + retval = -EINVAL; + goto unlock; + } + + if (result == 1) + touchsim_start(&info->touchsim); + else if (result == 0) + touchsim_stop(&info->touchsim); + else + pr_err("%s:Invalid cmd(%u). valid cmds are either 0 or 1!\n", + __func__, result); +unlock: + mutex_unlock(&info->diag_cmd_lock); +out: + return retval; +} + +/** sysfs file node to show motion filter mode + * "echo 0/1 > default_mf" to change + * "cat default_mf" to show + * Possible commands: + * 0 = Dynamic change motion filter + * 1 = Default motion filter by FW + */ +static ssize_t fts_default_mf_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fts_ts_info *info = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", info->use_default_mf ? 1 : 0); +} + +static ssize_t fts_default_mf_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct fts_ts_info *info = dev_get_drvdata(dev); + bool val = false; + ssize_t retval = count; + + if (!mutex_trylock(&info->diag_cmd_lock)) { + pr_err("%s: Blocking concurrent access\n", __func__); + retval = -EBUSY; + goto out; + } + + if (kstrtobool(buf, &val) < 0) { + pr_err("%s: bad input. valid inputs are either 0 or 1!\n", + __func__); + retval = -EINVAL; + goto unlock; + } + + info->use_default_mf = val; + +unlock: + mutex_unlock(&info->diag_cmd_lock); +out: + return retval; +} + +/***************************************** PRODUCTION TEST + ***************************************************/ + +/** + * File node to execute the Mass Production Test or to get data from the IC + * (raw or ms/ss init data) + * echo cmd > stm_fts_cmd to execute a command \n + * cat stm_fts_cmd to show the result of the command \n + * echo cmd > stm_fts_cmd; cat stm_fts_cmd to execute and show the result + * in just one call \n + * the string returned in the shell is made up as follow: \n + * { = start byte \n + * X1X2X3X4 = 4 bytes in HEX format which represent an error_code (00000000 = + * OK)\n + * (optional) data = data coming from the command executed represented as HEX + * string \n + * Not all the command return additional data \n + * } = end byte \n + * \n + * Possible commands (cmd): \n + * - 00 = MP Test -> return error_code \n + * - 01 = ITO Test -> return error_code \n + * - 03 = MS Raw Test -> return error_code \n + * - 04 = MS Init Data Test -> return error_code \n + * - 05 = SS Raw Test -> return error_code \n + * - 06 = SS Init Data Test -> return error_code \n + * - 13 = Read 1 MS Raw Frame -> return additional data: MS frame row after row + * \n + * - 14 = Read MS Init Data -> return additional data: MS init data row after + * row \n + * - 15 = Read 1 SS Raw Frame -> return additional data: SS frame, + * force channels followed by sense channels \n + * - 16 = Read SS Init Data -> return additional data: SS Init data, + * first IX for force and sense channels and then CX for force and sense + * channels \n + * - F0 = Perform a system reset -> return error_code \n + * - F1 = Perform a system reset and reenable the sensing and the interrupt + */ +static ssize_t stm_fts_cmd_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + u8 result, n = 0; + struct fts_ts_info *info = dev_get_drvdata(dev); + char *p, *temp_buf, *token; + size_t token_len = 0; + ssize_t retval = count; + + if (!count) { + pr_err("%s: Invalid input buffer length!\n", __func__); + retval = -EINVAL; + goto out; + } + + if (!info) { + pr_err("%s: Unable to access driver data\n", __func__); + retval = -EINVAL; + goto out; + } + + if (!mutex_trylock(&info->diag_cmd_lock)) { + pr_err("%s: Blocking concurrent access\n", __func__); + retval = -EBUSY; + goto out; + } + + memset(typeOfCommand, 0, sizeof(typeOfCommand)); + + temp_buf = kstrdup(buf, GFP_KERNEL); + if (!temp_buf) { + pr_err("%s: memory allocation failed!", + __func__); + retval = -ENOMEM; + goto unlock; + } + + p = temp_buf; + + /* Parse the input string to retrieve 2 hex-digit width cmds/args + * separated by one or more spaces. + * Any input not equal to 2 hex-digit width are ignored. + * A single 2 hex-digit width command w/ or w/o space is allowed. + * Inputs not in the valid hex range are also ignored. + * In case of encountering any of the above failure, the entire input + * buffer is discarded. + */ + while (p && (n < CMD_STR_LEN)) { + + while (isspace(*p)) { + p++; + } + + token = strsep(&p, " "); + + if (!token || *token == '\0') { + break; + } + + token_len = strlen(token); + + /* handle last token case */ + if (token_len == 3 && token[2] == '\n') + token[2] = '\0'; + else if (token_len != 2) { + pr_err("%s: bad len. len=%zu\n", + __func__, token_len); + n = 0; + break; + } + + if (kstrtou8(token, 16, &result)) { + /* Conversion failed due to bad input. + * Discard the entire buffer. + */ + pr_err("%s: bad input\n", __func__); + n = 0; + break; + } + + /* found a valid cmd/args */ + typeOfCommand[n] = result; + pr_info("%s: typeOfCommand[%d]=%02X\n", + __func__, n, typeOfCommand[n]); + + n++; + } + + if (n == 0) { + pr_err("%s: Found invalid cmd/arg\n", __func__); + retval = -EINVAL; + } + + numberParameters = n; + pr_info("%s: Number of Parameters = %d\n", __func__, numberParameters); + + kfree(temp_buf); + +unlock: + mutex_unlock(&info->diag_cmd_lock); +out: + return retval; +} + +static ssize_t stm_fts_cmd_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int res, j, doClean = 0, index = 0; + int size = (6 * 2) + 1; + int nodes = 0; + int init_type = SPECIAL_PANEL_INIT; + u8 *all_strbuff = buf; + struct fts_ts_info *info = dev_get_drvdata(dev); + const char *limits_file = info->board->limits_name; + + MutualSenseData compData; + SelfSenseData comData; + MutualSenseFrame frameMS; + SelfSenseFrame frameSS; + + u8 report = 0; + + if (!info) { + pr_err("%s: Unable to access driver data\n", __func__); + return -EINVAL; + } + + if (!mutex_trylock(&info->diag_cmd_lock)) { + pr_err("%s: Blocking concurrent access\n", __func__); + return -EBUSY; + } + + if (fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, true) < 0) { + res = ERROR_BUS_WR; + pr_err("%s: bus is not accessible.\n", __func__); + scnprintf(buf, PAGE_SIZE, "{ %08X }\n", res); + fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, false); + mutex_unlock(&info->diag_cmd_lock); + return 0; + } + + if (numberParameters >= 1) { + res = fts_enableInterrupt(false); + if (res < 0) { + pr_err("fts_enableInterrupt: ERROR %08X\n", res); + res = (res | ERROR_DISABLE_INTER); + goto END; + } + + switch (typeOfCommand[0]) { + /*ITO TEST*/ + case 0x01: + frameMS.node_data = NULL; + res = production_test_ito(limits_file, &tests, + &frameMS); + /* report MS raw frame only if was successfully + * acquired */ + if (frameMS.node_data != NULL) { + size += (frameMS.node_data_size * + sizeof(short) + 2) * 2; + report = 1; + } + break; + + /*PRODUCTION TEST*/ + case 0x02: + if (systemInfo.u8_cfgAfeVer != systemInfo.u8_cxAfeVer) { + res = ERROR_OP_NOT_ALLOW; + pr_err("Miss match in CX version! MP test not allowed with wrong CX memory! ERROR %08X\n", + res); + break; + } + res = production_test_initialization(init_type); + break; + + case 0x00: +#ifndef COMPUTE_INIT_METHOD + if (systemInfo.u8_cfgAfeVer != systemInfo.u8_cxAfeVer) { + res = ERROR_OP_NOT_ALLOW; + pr_err("Miss match in CX version! MP test not allowed with wrong CX memory! ERROR %08X\n", + res); + break; + } +#else + if (systemInfo.u8_mpFlag != MP_FLAG_FACTORY) { + init_type = SPECIAL_FULL_PANEL_INIT; + pr_info("Select Full Panel Init!\n"); + } else { + init_type = NO_INIT; + pr_info("Skip Full Panel Init!\n"); + } +#endif + res = production_test_main(limits_file, 1, init_type, + &tests, MP_FLAG_FACTORY); + break; + + /*read mutual raw*/ + case 0x13: + pr_info("Get 1 MS Frame\n"); + if (numberParameters >= 2 && + typeOfCommand[1] == LOCKED_LP_ACTIVE) + setScanMode(SCAN_MODE_LOCKED, LOCKED_LP_ACTIVE); + else + setScanMode(SCAN_MODE_LOCKED, LOCKED_ACTIVE); + msleep(WAIT_FOR_FRESH_FRAMES); + /* Skip sensing off when typeOfCommand[2]=0x01 + * to avoid sense on force cal after reading raw data + */ + if (!(numberParameters >= 3 && + typeOfCommand[2] == 0x01)) { + setScanMode(SCAN_MODE_ACTIVE, 0x00); + msleep(WAIT_AFTER_SENSEOFF); + /* Delete the events related to some touch + * (allow to call this function while touching + * the screen without having a flooding of the + * FIFO) + */ + flushFIFO(); + } +#ifdef READ_FILTERED_RAW + res = getMSFrame3(MS_FILTER, &frameMS); +#else + res = getMSFrame3(MS_RAW, &frameMS); +#endif + if (res < 0) { + pr_err("Error while taking the MS frame... ERROR %08X\n", + res); + } else { + pr_info("The frame size is %d words\n", + res); +#ifdef RAW_DATA_FORMAT_DEC + size += 3 * 2 + + (7 * frameMS.header.sense_node + 1) + * frameMS.header.force_node; +#else + size += (res * sizeof(short) + 2) * 2; +#endif + /* set res to OK because if getMSFrame is + * successful res = number of words read + */ + res = OK; + print_frame_short( + "MS frame =", + array1dTo2d_short( + frameMS.node_data, + frameMS.node_data_size, + frameMS.header.sense_node), + frameMS.header.force_node, + frameMS.header.sense_node); + } + break; + /*read self raw*/ + case 0x15: + pr_info("Get 1 SS Frame\n"); + if (numberParameters >= 2 && + typeOfCommand[1] == LOCKED_LP_DETECT) + setScanMode(SCAN_MODE_LOCKED, LOCKED_LP_DETECT); + else + setScanMode(SCAN_MODE_LOCKED, LOCKED_ACTIVE); + msleep(WAIT_FOR_FRESH_FRAMES); + /* Skip sensing off when typeOfCommand[2]=0x01 + * to avoid sense on force cal after reading raw data + */ + if (!(numberParameters >= 3 && + typeOfCommand[2] == 0x01)) { + setScanMode(SCAN_MODE_ACTIVE, 0x00); + msleep(WAIT_AFTER_SENSEOFF); + flushFIFO(); + /* delete the events related to some touch + * (allow to call this function while touching + * the screen without having a flooding of the + * FIFO) + */ + } + if (numberParameters >= 2 && + typeOfCommand[1] == LOCKED_LP_DETECT) +#ifdef READ_FILTERED_RAW + res = getSSFrame3(SS_DETECT_FILTER, &frameSS); +#else + res = getSSFrame3(SS_DETECT_RAW, &frameSS); +#endif + else +#ifdef READ_FILTERED_RAW + res = getSSFrame3(SS_FILTER, &frameSS); +#else + res = getSSFrame3(SS_RAW, &frameSS); +#endif + if (res < OK) { + pr_err("Error while taking the SS frame... ERROR %08X\n", + res); + } else { + pr_info("The frame size is %d words\n", res); +#ifdef RAW_DATA_FORMAT_DEC + size += 3 * 2 + 5 + + (frameSS.header.sense_node + + frameSS.header.force_node) * 7; +#else + size += (res * sizeof(short) + 2) * 2; +#endif + /* set res to OK because if getMSFrame is + * successful res = number of words read + */ + res = OK; + print_frame_short( + "SS force frame =", + array1dTo2d_short( + frameSS.force_data, + frameSS.header.force_node, + 1), + frameSS.header.force_node, 1); + print_frame_short( + "SS sense frame =", + array1dTo2d_short( + frameSS.sense_data, + frameSS.header.sense_node, + frameSS.header.sense_node), + 1, frameSS.header.sense_node); + } + break; + + case 0x14: /* read mutual comp data */ + pr_info("Get MS Compensation Data\n"); + res = readMutualSenseCompensationData(LOAD_CX_MS_TOUCH, + &compData); + + if (res < 0) + pr_err("Error reading MS compensation data ERROR %08X\n", + res); + else { + pr_info("MS Compensation Data Reading Finished!\n"); + size += ((compData.node_data_size + 3) * + sizeof(u8)) * 2; + print_frame_i8("MS Data (Cx2) =", + array1dTo2d_i8( + compData.node_data, + compData. + node_data_size, + compData.header. + sense_node), + compData.header.force_node, + compData.header.sense_node); + } + break; + + case 0x16: /* read self comp data */ + pr_info("Get SS Compensation Data...\n"); + res = readSelfSenseCompensationData(LOAD_CX_SS_TOUCH, + &comData); + if (res < 0) + pr_err("Error reading SS compensation data ERROR %08X\n", + res); + else { + pr_info("SS Compensation Data Reading Finished!\n"); + size += ((comData.header.force_node + + comData.header.sense_node) * 2 + 8) * + sizeof(u8) * 2; + print_frame_u8("SS Data Ix2_fm = ", + array1dTo2d_u8(comData.ix2_fm, + comData.header. + force_node, 1), + comData.header.force_node, 1); + print_frame_i8("SS Data Cx2_fm = ", + array1dTo2d_i8(comData.cx2_fm, + comData.header. + force_node, 1), + comData.header.force_node, 1); + print_frame_u8("SS Data Ix2_sn = ", + array1dTo2d_u8(comData.ix2_sn, + comData.header. + sense_node, + comData.header. + sense_node), 1, + comData.header.sense_node); + print_frame_i8("SS Data Cx2_sn = ", + array1dTo2d_i8(comData.cx2_sn, + comData.header. + sense_node, + comData.header. + sense_node), 1, + comData.header.sense_node); + } + break; + case 0x17: /* Read mutual strength */ + pr_info("Get 1 MS Strength\n"); + /* Skip sensing off when typeOfCommand[1]=0x01 + * to avoid sense on force cal after reading raw data + */ + if (!(numberParameters >= 2 && + typeOfCommand[1] == 0x01)) { + setScanMode(SCAN_MODE_ACTIVE, 0xFF); + msleep(WAIT_FOR_FRESH_FRAMES); + setScanMode(SCAN_MODE_ACTIVE, 0x00); + msleep(WAIT_AFTER_SENSEOFF); + /* Flush outstanding touch events */ + flushFIFO(); + } + nodes = getMSFrame3(MS_STRENGTH, &frameMS); + if (nodes < 0) { + res = nodes; + pr_err("Error while taking the MS strength... ERROR %08X\n", + res); + } else { + pr_info("The frame size is %d words\n", nodes); +#ifdef RAW_DATA_FORMAT_DEC + size += 3 * 2 + + (7 * frameMS.header.sense_node + 1) + * frameMS.header.force_node; +#else + size += (nodes * sizeof(short) + 2) * 2; +#endif + print_frame_short("MS strength =", + array1dTo2d_short(frameMS.node_data, + frameMS.node_data_size, + frameMS.header.sense_node), + frameMS.header.force_node, + frameMS.header.sense_node); + res = OK; + } + break; + case 0x03: /* MS Raw DATA TEST */ + res = fts_system_reset(); + if (res >= OK) + res = production_test_ms_raw(limits_file, 1, + &tests); + break; + + case 0x04: /* MS CX DATA TEST */ + res = fts_system_reset(); + if (res >= OK) + res = production_test_ms_cx(limits_file, 1, + &tests); + break; + + case 0x05: /* SS RAW DATA TEST */ + res = fts_system_reset(); + if (res >= OK) + res = production_test_ss_raw(limits_file, 1, + &tests); + break; + + case 0x06: /* SS IX CX DATA TEST */ + res = fts_system_reset(); + if (res >= OK) + res = production_test_ss_ix_cx(limits_file, 1, + &tests); + break; + + + case 0xF0: + case 0xF1: /* TOUCH ENABLE/DISABLE */ + doClean = (int)(typeOfCommand[0] & 0x01); + res = cleanUp(doClean); + break; + + default: + pr_err("COMMAND NOT VALID!! Insert a proper value ...\n"); + res = ERROR_OP_NOT_ALLOW; + break; + } + + doClean = fts_mode_handler(info, 1); + if (typeOfCommand[0] != 0xF0) + doClean |= fts_enableInterrupt(true); + if (doClean < 0) + pr_err("%s: ERROR %08X\n", __func__, + (doClean | ERROR_ENABLE_INTER)); + } else { + pr_err("NO COMMAND SPECIFIED!!! do: 'echo [cmd_code] [args] > stm_fts_cmd' before looking for result!\n"); + res = ERROR_OP_NOT_ALLOW; + } + +END: + /* here start the reporting phase, assembling the data + * to send in the file node */ + size = PAGE_SIZE; + index = 0; + index += scnprintf(all_strbuff + index, size - index, "{ %08X", res); + + if (res >= OK || report) { + /*all the other cases are already fine printing only the res.*/ + switch (typeOfCommand[0]) { + case 0x01: + case 0x13: + case 0x17: + + if (frameMS.node_data == NULL) + break; + +#ifdef RAW_DATA_FORMAT_DEC + index += scnprintf(all_strbuff + index, size - index, + "%3d", + (u8)frameMS.header.force_node); + index += scnprintf(all_strbuff + index, size - index, + "%3d", + (u8)frameMS.header.sense_node); +#else + index += scnprintf(all_strbuff + index, + size - index, "%02X", + (u8)frameMS.header.force_node); + + index += scnprintf(all_strbuff + index, + size - index, "%02X", + (u8)frameMS.header.sense_node); +#endif + + for (j = 0; j < frameMS.node_data_size; j++) { +#ifdef RAW_DATA_FORMAT_DEC + if (j % frameMS.header.sense_node == 0) + index += scnprintf(all_strbuff + index, + size - index, "\n"); + index += scnprintf(all_strbuff + index, + size - index, "%d ", + frameMS.node_data[j]); +#else + index += scnprintf(all_strbuff + index, + size - index, + "%02X%02X", + (frameMS.node_data[j] & 0xFF00) >> 8, + frameMS.node_data[j] & 0xFF); +#endif + } + + kfree(frameMS.node_data); + break; + + case 0x15: +#ifdef RAW_DATA_FORMAT_DEC + index += scnprintf(all_strbuff + index, size - index, + "%3d", + (u8)frameSS.header.force_node); + index += scnprintf(all_strbuff + index, size - index, + "%3d", + (u8)frameSS.header.sense_node); + index += scnprintf(all_strbuff + index, size - index, + "\n"); +#else + index += scnprintf(all_strbuff + index, + size - index, "%02X", + (u8)frameSS.header.force_node); + + index += scnprintf(all_strbuff + index, + size - index, "%02X", + (u8)frameSS.header.sense_node); +#endif + + /* Copying self raw data Force */ + for (j = 0; j < frameSS.header.force_node; j++) { +#ifdef RAW_DATA_FORMAT_DEC + index += scnprintf(all_strbuff + index, + size - index, + "%d ", + frameSS.force_data[j]); +#else + index += scnprintf(all_strbuff + index, + size - index, + "%02X%02X", + (frameSS.force_data[j] & 0xFF00) >> 8, + frameSS.force_data[j] & 0xFF); +#endif + } + + + +#ifdef RAW_DATA_FORMAT_DEC + index += scnprintf(all_strbuff + index, size - index, + "\n"); +#endif + + /* Copying self raw data Sense */ + for (j = 0; j < frameSS.header.sense_node; j++) { +#ifdef RAW_DATA_FORMAT_DEC + index += scnprintf(all_strbuff + index, + size - index, "%d ", + frameSS.sense_data[j]); +#else + index += scnprintf(all_strbuff + index, + size - index, + "%02X%02X", + (frameSS.sense_data[j] & 0xFF00) >> 8, + frameSS.sense_data[j] & 0xFF); +#endif + } + + kfree(frameSS.force_data); + kfree(frameSS.sense_data); + break; + + case 0x14: + index += scnprintf(all_strbuff + index, + size - index, "%02X", + (u8)compData.header.force_node); + + index += scnprintf(all_strbuff + index, + size - index, "%02X", + (u8)compData.header.sense_node); + + /* Cpying CX1 value */ + index += scnprintf(all_strbuff + index, + size - index, "%02X", + (compData.cx1) & 0xFF); + + /* Copying CX2 values */ + for (j = 0; j < compData.node_data_size; j++) { + index += scnprintf(all_strbuff + index, + size - index, + "%02X", + (compData.node_data[j]) & 0xFF); + } + + kfree(compData.node_data); + break; + + case 0x16: + index += scnprintf(all_strbuff + index, + size - index, "%02X", + comData.header.force_node); + + index += scnprintf(all_strbuff + index, + size - index, "%02X", + comData.header.sense_node); + + index += scnprintf(all_strbuff + index, + size - index, "%02X", + (comData.f_ix1) & 0xFF); + + index += scnprintf(all_strbuff + index, + size - index, "%02X", + (comData.s_ix1) & 0xFF); + + index += scnprintf(all_strbuff + index, + size - index, "%02X", + (comData.f_cx1) & 0xFF); + + index += scnprintf(all_strbuff + index, + size - index, "%02X", + (comData.s_cx1) & 0xFF); + + index += scnprintf(all_strbuff + index, + size - index, "%02X", + (comData.f_ix0) & 0xFF); + + index += scnprintf(all_strbuff + index, + size - index, "%02X", + (comData.s_ix0) & 0xFF); + + /* Copying IX2 Force */ + for (j = 0; j < comData.header.force_node; j++) { + index += scnprintf(all_strbuff + index, + size - index, + "%02X", + comData.ix2_fm[j] & 0xFF); + } + + /* Copying IX2 Sense */ + for (j = 0; j < comData.header.sense_node; j++) { + index += scnprintf(all_strbuff + index, + size - index, + "%02X", + comData.ix2_sn[j] & 0xFF); + } + + /* Copying CX2 Force */ + for (j = 0; j < comData.header.force_node; j++) { + index += scnprintf(all_strbuff + index, + size - index, + "%02X", + comData.cx2_fm[j] & 0xFF); + } + + /* Copying CX2 Sense */ + for (j = 0; j < comData.header.sense_node; j++) { + index += scnprintf(all_strbuff + index, + size - index, + "%02X", + comData.cx2_sn[j] & 0xFF); + } + + kfree(comData.ix2_fm); + kfree(comData.ix2_sn); + kfree(comData.cx2_fm); + kfree(comData.cx2_sn); + break; + + default: + break; + } + } + + index += scnprintf(all_strbuff + index, size - index, " }\n"); + numberParameters = 0; + /* need to reset the number of parameters in order to wait the + * next command, comment if you want to repeat the last command sent + * just doing a cat */ + /* pr_err("numberParameters = %d\n", numberParameters); */ + + fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, false); + mutex_unlock(&info->diag_cmd_lock); + + return index; +} + +static ssize_t fts_autotune_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fts_ts_info *info = dev_get_drvdata(dev); + int ret = 0; + bool val = false; + + if ((kstrtobool(buf, &val) < 0) || !val) { + ret = -EINVAL; + goto err_args; + } + + fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, true); + + ret = production_test_main(info->board->limits_name, 1, + SPECIAL_FULL_PANEL_INIT, &tests, + MP_FLAG_BOOT); + + cleanUp(true); + + info->autotune_stat = ret; + + fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, false); + +err_args: + + return ret < 0 ? ret : count; +} + +static ssize_t fts_autotune_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fts_ts_info *info = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "{ %08X }\n", info->autotune_stat); +} + +static ssize_t fts_infoblock_getdata_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fts_ts_info *info = dev_get_drvdata(dev); + int count = 0; + int res = 0; + u8 control_reg = 0; + u8 flash_status = 0; + u8 *data = NULL; + int addr = 0; + int i = 0; + + fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, true); + + res = fts_writeReadU8UX(FTS_CMD_HW_REG_R, ADDR_SIZE_HW_REG, + ADDR_FLASH_STATUS, &control_reg, + 1, DUMMY_HW_REG); + + if (res < OK) { + count += scnprintf(&buf[count], PAGE_SIZE - count, + "ADDR_FLASH_STATUS read failed\n"); + goto END; + } + flash_status = (control_reg & 0xFC); + + count += scnprintf(&buf[count], PAGE_SIZE - count, + "The value:0x%X 0x%X\n", control_reg, flash_status); + + res = fts_writeU8UX(FTS_CMD_HW_REG_W, ADDR_SIZE_HW_REG, + ADDR_FLASH_STATUS, &flash_status, 1); + if (res < OK) { + count += scnprintf(&buf[count], PAGE_SIZE - count, + "ADDR_FLASH_STATUS write failed\n"); + goto END; + } + data = kmalloc(INFO_BLOCK_SIZE * sizeof(u8), GFP_KERNEL); + if (data == NULL) { + count += scnprintf(&buf[count], PAGE_SIZE - count, + "kmalloc failed\n"); + goto END; + } + res = fts_writeReadU8UX(FTS_CMD_HW_REG_R, ADDR_SIZE_HW_REG, + ADDR_INFOBLOCK, data, INFO_BLOCK_SIZE, + DUMMY_HW_REG); + if (res < OK) { + count += scnprintf(&buf[count], PAGE_SIZE - count, + "ADDR_INFOBLOCK read failed\n"); + goto END; + } + addr = INFO_BLOCK_LOCKDOWN; + count += scnprintf(&buf[count], PAGE_SIZE - count, + "Lock down info the first 4bytes:0X%02X%02X%02X%02X\n", + data[addr+3], data[addr+2], data[addr+1], + data[addr]); + addr += 4; + count += scnprintf(&buf[count], PAGE_SIZE - count, + "Lock down info the second 4bytes:0X%02X%02X%02X%02X\n", + data[addr+3], data[addr+2], data[addr+1], + data[addr]); + count += scnprintf(&buf[count], PAGE_SIZE - count, "0x%04X\n", addr); + addr = INFO_BLOCK_AOFFSET; + count += scnprintf(&buf[count], PAGE_SIZE - count, + "Aoffset magic number:0x%02X%02X%02X%02X\n", + data[addr+3], data[addr+2], data[addr+1], + data[addr]); + addr += 4; + count += scnprintf(&buf[count], PAGE_SIZE - count, + "Aoffset crc:0x%02X%02X%02X%02X\n", + data[addr+3], data[addr+2], data[addr+1], + data[addr]); + addr += 4; + count += scnprintf(&buf[count], PAGE_SIZE - count, + "Aoffset ~crcr:0x%02X%02X%02X%02X\n", + data[addr+3], data[addr+2], data[addr+1], + data[addr]); + addr += 4; + count += scnprintf(&buf[count], PAGE_SIZE - count, + "Aoffset len:0x%02X%02X%02X%02X\n", + data[addr+3], data[addr+2], data[addr+1], + data[addr]); + addr += 4; + count += scnprintf(&buf[count], PAGE_SIZE - count, + "Aoffset ~len:0x%02X%02X%02X%02X\n", + data[addr+3], data[addr+2], data[addr+1], + data[addr]); + addr += 4; + count += scnprintf(&buf[count], PAGE_SIZE - count, + "Aoffset ver:0x%02X%02X%02X%02X\n", + data[addr+3], data[addr+2], data[addr+1], + data[addr]); + addr += 4; + count += scnprintf(&buf[count], PAGE_SIZE - count, "0x%04X\n", addr); + for (i = 0; i < 38; i++) { + count += scnprintf(&buf[count], PAGE_SIZE - count, + "Aoffset CH[%d] Quar:0X%02X,Half:0X%02X,Full:0X%02X%02X\n", + i, data[addr+3], data[addr+2], data[addr+1], + data[addr]); + addr += 4; + } + count += scnprintf(&buf[count], PAGE_SIZE - count, "0x%04X\n", addr); + for (i = 0; i < 4; i++) { + count += scnprintf(&buf[count], PAGE_SIZE - count, + "Aoffset CA[%d] Quar:0X%02X,Half:0X%02X,Full:0X%02X%02X\n", + i, data[addr+3], data[addr+2], data[addr+1], + data[addr]); + addr += 4; + } + addr = INFO_BLOCK_OSC; + count += scnprintf(&buf[count], PAGE_SIZE - count, + "OscTrim magic number:0x%02X%02X%02X%02X\n", + data[addr+3], data[addr+2], data[addr+1], + data[addr]); + addr += 4; + count += scnprintf(&buf[count], PAGE_SIZE - count, + "OscTrim crc:0x%02X%02X%02X%02X\n", + data[addr+3], data[addr+2], data[addr+1], + data[addr]); + addr += 4; + count += scnprintf(&buf[count], PAGE_SIZE - count, + "OscTrim len:0x%02X%02X%02X%02X\n", + data[addr+3], data[addr+2], data[addr+1], + data[addr]); + addr += 4; + count += scnprintf(&buf[count], PAGE_SIZE - count, + "OscTrim ~crcr:0x%02X%02X%02X%02X\n", + data[addr+3], data[addr+2], data[addr+1], + data[addr]); + addr += 4; + count += scnprintf(&buf[count], PAGE_SIZE - count, + "OscTrim ~len:0x%02X%02X%02X%02X\n", + data[addr+3], data[addr+2], data[addr+1], + data[addr]); + addr += 4; + count += scnprintf(&buf[count], PAGE_SIZE - count, + "OscTrim ver:0x%02X%02X%02X%02X\n", + data[addr+3], data[addr+2], data[addr+1], + data[addr]); + addr += 4; + count += scnprintf(&buf[count], PAGE_SIZE - count, + "OscTrim major ver:0x%02X%02X%02X%02X\n", + data[addr+3], data[addr+2], data[addr+1], + data[addr]); + addr += 4; + count += scnprintf(&buf[count], PAGE_SIZE - count, + "OscTrim cen bg:0x%02X%02X%02X%02X\n", + data[addr+3], data[addr+2], data[addr+1], + data[addr]); + addr += 4; + count += scnprintf(&buf[count], PAGE_SIZE - count, + "OscTrim frequency bg:0x%02X%02X%02X%02X\n", + data[addr+3], data[addr+2], data[addr+1], + data[addr]); + addr += 4; + count += scnprintf(&buf[count], PAGE_SIZE - count, + "OscTrim frequency afe:0x%02X%02X%02X%02X\n", + data[addr+3], data[addr+2], data[addr+1], + data[addr]); + addr += 4; + count += scnprintf(&buf[count], PAGE_SIZE - count, + "OscTrim cen bg valid:0x%02X%02X%02X%02X\n", + data[addr+3], data[addr+2], data[addr+1], + data[addr]); + addr += 4; + count += scnprintf(&buf[count], PAGE_SIZE - count, + "OscTrim cen afe valid:0x%02X%02X%02X%02X\n", + data[addr+3], data[addr+2], data[addr+1], + data[addr]); + +END: + kfree(data); + + if (control_reg != flash_status) + fts_writeU8UX(FTS_CMD_HW_REG_W, ADDR_SIZE_HW_REG, + ADDR_FLASH_STATUS, &control_reg, 1); + + fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, false); + + return count; +} + +/* sysfs file node to store heatmap mode + * "echo cmd > heatmap_mode" to change + * Possible commands: + * 0 = FTS_HEATMAP_OFF + * 1 = FTS_HEATMAP_PARTIAL + * 2 = FTS_HEATMAP_FULL + */ +#ifdef TOUCHSCREEN_HEATMAP +static ssize_t fts_heatmap_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fts_ts_info *info = dev_get_drvdata(dev); + int result; + int val; + + result = kstrtoint(buf, 10, &val); + if (result < 0 || val < FTS_HEATMAP_OFF || val > FTS_HEATMAP_FULL) { + pr_err("%s: Invalid input.\n", __func__); + return -EINVAL; + } + + info->heatmap_mode = val; + return count; +} + +static ssize_t fts_heatmap_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fts_ts_info *info = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", + info->heatmap_mode); +} +#endif + +static DEVICE_ATTR(infoblock_getdata, (0444), + fts_infoblock_getdata_show, NULL); +static DEVICE_ATTR(fwupdate, 0664, fts_fwupdate_show, + fts_fwupdate_store); +static DEVICE_ATTR(appid, 0444, fts_appid_show, NULL); +static DEVICE_ATTR(mode_active, 0444, fts_mode_active_show, NULL); +static DEVICE_ATTR(fw_file_test, 0444, fts_fw_test_show, NULL); +static DEVICE_ATTR(status, 0444, fts_status_show, NULL); +static DEVICE_ATTR(stm_fts_cmd, 0664, stm_fts_cmd_show, + stm_fts_cmd_store); +#ifdef TOUCHSCREEN_HEATMAP +static DEVICE_ATTR(heatmap_mode, 0664, fts_heatmap_mode_show, + fts_heatmap_mode_store); +#endif +#ifdef USE_ONE_FILE_NODE +static DEVICE_ATTR(feature_enable, 0664, + fts_feature_enable_show, fts_feature_enable_store); +#else + + +#ifdef GRIP_MODE +static DEVICE_ATTR(grip_mode, 0664, fts_grip_mode_show, + fts_grip_mode_store); +#endif + +#ifdef CHARGER_MODE +static DEVICE_ATTR(charger_mode, 0664, + fts_charger_mode_show, fts_charger_mode_store); +#endif + +#ifdef GLOVE_MODE +static DEVICE_ATTR(glove_mode, 0664, + fts_glove_mode_show, fts_glove_mode_store); +#endif + +#ifdef COVER_MODE +static DEVICE_ATTR(cover_mode, 0664, + fts_cover_mode_show, fts_cover_mode_store); +#endif + +#ifdef STYLUS_MODE +static DEVICE_ATTR(stylus_mode, 0664, + fts_stylus_mode_show, fts_stylus_mode_store); +#endif + +#endif + +#ifdef GESTURE_MODE +static DEVICE_ATTR(gesture_mask, 0664, + fts_gesture_mask_show, fts_gesture_mask_store); +static DEVICE_ATTR(gesture_coordinates, 0664, + fts_gesture_coordinates_show, NULL); +#endif +static DEVICE_ATTR(autotune, 0664, fts_autotune_show, fts_autotune_store); + +static DEVICE_ATTR(touchsim, 0664, + fts_touch_simulation_show, + fts_touch_simulation_store); + +static DEVICE_ATTR(default_mf, 0664, + fts_default_mf_show, + fts_default_mf_store); + +#ifdef SUPPORT_PROX_PALM +static DEVICE_ATTR(audio_status, 0644, + audio_status_show, + audio_status_store); + +static DEVICE_ATTR(prox_palm_status, 0644, + prox_palm_status_show, + prox_palm_status_store); +#endif + +/* /sys/devices/soc.0/f9928000.i2c/i2c-6/6-0049 */ +static struct attribute *fts_attr_group[] = { + &dev_attr_infoblock_getdata.attr, + &dev_attr_fwupdate.attr, + &dev_attr_appid.attr, + &dev_attr_mode_active.attr, + &dev_attr_fw_file_test.attr, + &dev_attr_status.attr, + &dev_attr_stm_fts_cmd.attr, +#ifdef TOUCHSCREEN_HEATMAP + &dev_attr_heatmap_mode.attr, +#endif +#ifdef USE_ONE_FILE_NODE + &dev_attr_feature_enable.attr, +#else + +#ifdef GRIP_MODE + &dev_attr_grip_mode.attr, +#endif +#ifdef CHARGER_MODE + &dev_attr_charger_mode.attr, +#endif +#ifdef GLOVE_MODE + &dev_attr_glove_mode.attr, +#endif +#ifdef COVER_MODE + &dev_attr_cover_mode.attr, +#endif +#ifdef STYLUS_MODE + &dev_attr_stylus_mode.attr, +#endif + +#endif + +#ifdef GESTURE_MODE + &dev_attr_gesture_mask.attr, + &dev_attr_gesture_coordinates.attr, +#endif + &dev_attr_autotune.attr, + &dev_attr_touchsim.attr, + &dev_attr_default_mf.attr, +#ifdef SUPPORT_PROX_PALM + &dev_attr_prox_palm_status.attr, + &dev_attr_audio_status.attr, +#endif + NULL, +}; + +#ifdef SUPPORT_PROX_PALM +/* sysfs file node to store audio status + * "echo cmd > audio_status" to change + */ +static ssize_t audio_status_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fts_ts_info *info = dev_get_drvdata(dev); + int result; + int val; + + result = kstrtoint(buf, 10, &val); + if (result < 0) { + pr_err("%s: Invalid input.\n", __func__); + return -EINVAL; + } + + if (val == 0) { + fts_set_bus_ref(info, FTS_BUS_REF_PHONE_CALL, false); + } else { + fts_set_bus_ref(info, FTS_BUS_REF_PHONE_CALL, true); + sysfs_notify(&dev->kobj, NULL, dev_attr_prox_palm_status.attr.name); + } + info->audio_status = val; + pr_info("%s: audio status %d", __func__, val); + return count; +} + +static ssize_t audio_status_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fts_ts_info *info = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", + info->audio_status); +} + +static ssize_t prox_palm_status_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fts_ts_info *info = dev_get_drvdata(dev); + int result; + int val; + + result = kstrtoint(buf, 10, &val); + if (result < 0) { + pr_err("%s: Invalid input.\n", __func__); + return -EINVAL; + } + + info->prox_palm_status = val; + sysfs_notify(&dev->kobj, NULL, dev_attr_prox_palm_status.attr.name); + pr_info("%s Notify prox_palms status %d", __func__, + info->prox_palm_status); + return count; +} + +static ssize_t prox_palm_status_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fts_ts_info *info = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", + info->prox_palm_status); +} + +/** + * When the feature is enabled, touch IC will keep in active sensing mode + * but only report status event and gesture event and stop reporting + * pointer location event + */ +int enable_prox_palm_only_mode(bool enable) +{ + u8 cmd[3] = {0xC0, 0x0D, (u8) enable}; + int ret = 0; + + ret = fts_write(cmd, 3); + if (ret < 0) + pr_err("%s: %s failed, ret = %d", __func__, + enable ? "enable" : "disable", ret); + return ret; +} +#endif +/** @}*/ +/** @}*/ + + +/** + * @defgroup isr Interrupt Service Routine (Event Handler) + * The most important part of the driver is the ISR (Interrupt Service Routine) + * called also as Event Handler \n + * As soon as the interrupt pin goes low, fts_interrupt_handler() is called and + * the chain to read and parse the event read from the FIFO start.\n + * For any different kind of EVT_ID there is a specific event handler + * which will take the correct action to report the proper info to the host. \n + * The most important events are the one related to touch information, status + * update or user report. + * @{ + */ + +/** + * Report to the linux input system the pressure and release of a button + * handling concurrency + * @param info pointer to fts_ts_info which contains info about the device + * and its hw setup + * @param key_code button value + */ +void fts_input_report_key(struct fts_ts_info *info, int key_code) +{ + mutex_lock(&info->input_report_mutex); + input_report_key(info->input_dev, key_code, 1); + input_sync(info->input_dev); + input_report_key(info->input_dev, key_code, 0); + input_sync(info->input_dev); + mutex_unlock(&info->input_report_mutex); +} + + + +/** + * Event Handler for no events (EVT_ID_NOEVENT) + */ +static bool fts_nop_event_handler(struct fts_ts_info *info, unsigned + char *event) +{ + pr_info("%s: Doing nothing for event = %02X %02X %02X %02X %02X %02X %02X %02X\n", + __func__, event[0], event[1], event[2], event[3], + event[4], + event[5], event[6], event[7]); + return false; +} + +/** + * Event handler for enter and motion events (EVT_ID_ENTER_POINT, + * EVT_ID_MOTION_POINT ) + * report touch coordinates and additional information + * to the linux input system + */ +static bool fts_enter_pointer_event_handler(struct fts_ts_info *info, unsigned + char *event) +{ + unsigned char touchId; + unsigned int touch_condition = 1, tool = MT_TOOL_FINGER; + int x, y, z, major, minor, distance; + u8 touchType; + + if (!info->resume_bit) + goto no_report; + + touchType = event[1] & 0x0F; + touchId = (event[1] & 0xF0) >> 4; + + x = (((int)event[3] & 0x0F) << 8) | (event[2]); + y = ((int)event[4] << 4) | ((event[3] & 0xF0) >> 4); + z = (int)event[5]; + if (z <= 0) { + /* Should not happen, because zero pressure implies contact has + * left, so this function should not be invoked. For safety, to + * prevent this touch from being dropped, set to smallest + * pressure value instead + */ +#ifndef SKIP_PRESSURE + pr_debug("%s: Pressure is %i, but pointer is not leaving\n", + __func__, z); +#endif + z = 1; /* smallest non-zero pressure value */ + } + major = (int)(((event[0] & 0x0C) << 2) | ((event[6] & 0xF0) >> 4)); + minor = (int)(((event[7] & 0xC0) >> 2) | (event[6] & 0x0F)); + /* TODO: check with fw how they will report distance */ + distance = 0; /* if the tool is touching the display + * the distance should be 0 */ + + if (x > info->board->x_axis_max) + x = info->board->x_axis_max; + + if (y > info->board->y_axis_max) + y = info->board->y_axis_max; + + input_mt_slot(info->input_dev, touchId); + switch (touchType) { +#ifdef STYLUS_MODE + case TOUCH_TYPE_STYLUS: + pr_info("%s : It is a stylus!\n", __func__); + if (info->stylus_enabled == 1) { + /* if stylus_enabled is not ==1 + * it will be reported as normal touch */ + tool = MT_TOOL_PEN; + touch_condition = 1; + __set_bit(touchId, &info->stylus_id); + break; + } +#endif + /* TODO: customer can implement a different strategy for each kind of + * touch */ + case TOUCH_TYPE_FINGER: + case TOUCH_TYPE_GLOVE: + pr_debug("%s : It is a touch type %d!\n", __func__, touchType); + if (info->palm_touch_mask) + tool = MT_TOOL_PALM; + else + tool = MT_TOOL_FINGER; + touch_condition = 1; + __set_bit(touchId, &info->touch_id); + __clear_bit(touchId, &info->palm_touch_mask); + __clear_bit(touchId, &info->grip_touch_mask); + break; + case TOUCH_TYPE_PALM: + pr_debug("%s : It is a touch type %d!\n", __func__, touchType); + tool = MT_TOOL_PALM; + touch_condition = 1; + __set_bit(touchId, &info->touch_id); + __set_bit(touchId, &info->palm_touch_mask); + __clear_bit(touchId, &info->grip_touch_mask); + break; + case TOUCH_TYPE_GRIP: + pr_debug("%s : It is a touch type %d!\n", __func__, touchType); + tool = MT_TOOL_PALM; + touch_condition = 1; + __set_bit(touchId, &info->touch_id); + __clear_bit(touchId, &info->palm_touch_mask); + __set_bit(touchId, &info->grip_touch_mask); + break; + + case TOUCH_TYPE_HOVER: + tool = MT_TOOL_FINGER; + touch_condition = 0; /* need to hover */ + z = 0; /* no pressure */ + __set_bit(touchId, &info->touch_id); + distance = DISTANCE_MAX;/* check with fw report the hovering + * distance */ + break; + + default: + pr_err("%s : Invalid touch type = %d ! No Report...\n", + __func__, touchType); + goto no_report; + } + + input_report_key(info->input_dev, BTN_TOUCH, touch_condition); + input_mt_report_slot_state(info->input_dev, tool, 1); + + /* pr_info("%s : TouchID = %d,Touchcount = %d\n", __func__, + * touchId,touchcount); */ + + input_report_abs(info->input_dev, ABS_MT_POSITION_X, x); + input_report_abs(info->input_dev, ABS_MT_POSITION_Y, y); + input_report_abs(info->input_dev, ABS_MT_TOUCH_MAJOR, major); + input_report_abs(info->input_dev, ABS_MT_TOUCH_MINOR, minor); +#ifndef SKIP_PRESSURE + input_report_abs(info->input_dev, ABS_MT_PRESSURE, z); +#endif + +#ifndef SKIP_DISTANCE + input_report_abs(info->input_dev, ABS_MT_DISTANCE, distance); +#endif + /* pr_info("%s : Event 0x%02x - ID[%d], (x, y) = (%3d, %3d) + * Size = %d\n", + * __func__, *event, touchId, x, y, touchType); */ + + return true; +no_report: + return false; +} + +/** + * Event handler for leave event (EVT_ID_LEAVE_POINT ) + * Report to the linux input system that one touch left the display + */ +static bool fts_leave_pointer_event_handler(struct fts_ts_info *info, unsigned + char *event) +{ + unsigned char touchId; + unsigned int tool = MT_TOOL_FINGER; + u8 touchType; + + touchType = event[1] & 0x0F; + touchId = (event[1] & 0xF0) >> 4; + + input_mt_slot(info->input_dev, touchId); + + input_report_abs(info->input_dev, ABS_MT_PRESSURE, 0); + switch (touchType) { +#ifdef STYLUS_MODE + case TOUCH_TYPE_STYLUS: + pr_info("%s : It is a stylus!\n", __func__); + if (info->stylus_enabled == 1) { + /* if stylus_enabled is not ==1 it will be reported as + * normal touch */ + tool = MT_TOOL_PEN; + __clear_bit(touchId, &info->stylus_id); + break; + } +#endif + + case TOUCH_TYPE_FINGER: + /* pr_info("%s : It is a finger!\n", __func__); */ + case TOUCH_TYPE_GLOVE: + /* pr_info("%s : It is a glove!\n", __func__); */ + case TOUCH_TYPE_PALM: + /* pr_info("%s : It is a palm!\n", __func__); */ + case TOUCH_TYPE_GRIP: + /* pr_info("%s : It is a grip!\n", __func__); */ + case TOUCH_TYPE_HOVER: + tool = MT_TOOL_FINGER; + __clear_bit(touchId, &info->touch_id); + __clear_bit(touchId, &info->palm_touch_mask); + __clear_bit(touchId, &info->grip_touch_mask); + break; + + default: + pr_err("%s : Invalid touch type = %d ! No Report...\n", + __func__, touchType); + return false; + } + + input_mt_report_slot_state(info->input_dev, tool, 0); + + /* pr_info("%s : TouchID = %d, Touchcount = %d\n", __func__, + * touchId,touchcount); */ + + + input_report_abs(info->input_dev, ABS_MT_TRACKING_ID, -1); + /* pr_info("%s : Event 0x%02x - release ID[%d]\n", __func__, + * event[0], touchId); */ + return true; +} + +/* EventId : EVT_ID_MOTION_POINT */ +#define fts_motion_pointer_event_handler fts_enter_pointer_event_handler +/* remap the motion event handler to the same function which handle the enter + * event */ + +/** + * Event handler for error events (EVT_ID_ERROR) + * Handle unexpected error events implementing recovery strategy and + * restoring the sensing status that the IC had before the error occurred + */ +static bool fts_error_event_handler(struct fts_ts_info *info, unsigned + char *event) +{ + int error = 0; + + pr_info("%s: Received event %02X %02X %02X %02X %02X %02X %02X %02X\n", + __func__, event[0], event[1], event[2], event[3], event[4], + event[5], + event[6], event[7]); + + switch (event[1]) { + case EVT_TYPE_ERROR_ESD:/* esd */ + {/* before reset clear all slot */ + release_all_touches(info); + + fts_chip_powercycle(info); + + error = fts_system_reset(); + error |= fts_mode_handler(info, 0); + error |= fts_enableInterrupt(true); + if (error < OK) + pr_err("%s Cannot restore the device ERROR %08X\n", + __func__, error); + } + break; + case EVT_TYPE_ERROR_HARD_FAULT: /* hard fault */ + case EVT_TYPE_ERROR_WATCHDOG: /* watch dog timer */ + { + dumpErrorInfo(NULL, 0); + /* before reset clear all slots */ + release_all_touches(info); + error = fts_system_reset(); + error |= fts_mode_handler(info, 0); + error |= fts_enableInterrupt(true); + if (error < OK) + pr_err("%s Cannot reset the device ERROR %08X\n", + __func__, error); + } + break; + } + return false; +} + +/** + * Event handler for controller ready event (EVT_ID_CONTROLLER_READY) + * Handle controller events received after unexpected reset of the IC updating + * the resets flag and restoring the proper sensing status + */ +static bool fts_controller_ready_event_handler(struct fts_ts_info *info, + unsigned char *event) +{ + int error; + + pr_info("%s: Received event %02X %02X %02X %02X %02X %02X %02X %02X\n", + __func__, event[0], event[1], event[2], event[3], event[4], + event[5], event[6], event[7]); + release_all_touches(info); + setSystemResetedUp(1); + setSystemResetedDown(1); + error = fts_mode_handler(info, 0); + if (error < OK) + pr_err("%s Cannot restore the device status ERROR %08X\n", + __func__, error); + return false; +} + +/** + * Event handler for status events (EVT_ID_STATUS_UPDATE) + * Handle status update events + */ +static bool fts_status_event_handler(struct fts_ts_info *info, unsigned + char *event) +{ + u8 grid_touch_status; + + switch (event[1]) { + case EVT_TYPE_STATUS_ECHO: + pr_debug("%s: Echo event of command = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], event[5], + event[6], event[7]); + break; + + case EVT_TYPE_STATUS_GPIO_CHAR_DET: + pr_info("%s: GPIO Charger Detect =" + " %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], event[5], + event[6], event[7]); + break; + + case EVT_TYPE_STATUS_FORCE_CAL: + switch (event[2]) { + case 0x01: + pr_info("%s: Sense on Force cal = %02X %02X" + " %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + case 0x02: + pr_info("%s: Host command Force cal = %02X %02X" + " %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + case 0x10: + pr_info("%s: Mutual frame drop Force cal = %02X %02X" + " %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + case 0x11: + pr_info("%s: Mutual pure raw Force cal = %02X %02X" + " %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + case 0x20: + pr_info("%s: Self detect negative Force cal = %02X" + " %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + case 0x21: + pr_info("%s: Self touch negative Force cal = %02X" + " %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + case 0x22: + pr_info("%s: Self detect frame flatness Force cal =" + " %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + case 0x23: + pr_info("%s: Self touch frame flatness Force cal =" + " %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + case 0x30: + pr_info("%s: Invalid mutual Force cal = %02X" + " %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + case 0x31: + pr_info("%s: Invalid differential mutual Force cal =" + " %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + case 0x32: + pr_info("%s: Invalid Self Force cal = %02X" + " %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + case 0x33: + pr_info("%s: Invalid Self island Force cal = %02X" + " %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + case 0x34: + pr_info("%s: Invalid Self force touch Force cal =" + " %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + case 0x35: + pr_info("%s: Mutual frame flatness Force cal =" + " %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + default: + pr_info("%s: Unknown force cal = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + } + break; + + case EVT_TYPE_STATUS_FRAME_DROP: + pr_info("%s: Frame drop = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + case EVT_TYPE_STATUS_SS_RAW_SAT: + if (event[2] == 1) + pr_info("%s: SS Raw Saturated = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + else + pr_info("%s: SS Raw No more Saturated = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + case EVT_TYPE_STATUS_WATER: + switch (event[2]) { + case 0x00: + pr_info("%s: Water Mode Entry by BLD with real" + " raw frame = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + case 0x01: + pr_info("%s: Water Mode Entry by BLD with rom" + " raw frame = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + case 0x02: + pr_info("%s: Water Mode Entry by MID with real" + " raw frame = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + case 0x03: + pr_info("%s: Water Mode leave by BLD with real" + " raw frame = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + case 0x04: + pr_info("%s: Water Mode leave by BLD with rom" + " raw frame = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + case 0x05: + pr_info("%s: Water Mode leave by MID with real" + " raw frame = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + default: + pr_info("%s: Unknown water mode = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + } + break; + + case EVT_TYPE_STATUS_PRE_WAT_DET: + if (event[2] == 1) + pr_info("%s: Previous Water entry =" + " %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + else + pr_info("%s: Previous Water leave =" + " %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + case EVT_TYPE_STATUS_NOISE: + if(info->scanning_frequency != event[3]) { + pr_info("%s: Scanning frequency changed from %02X to %02X\n", + __func__, info->scanning_frequency, event[3]); + pr_info("%s: Noise Status Event = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], + event[4], event[5], event[6], event[7]); + info->scanning_frequency = event[3]; + } else { + pr_debug("%s: Noise Status Event = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + } + break; + + case EVT_TYPE_STATUS_STIMPAD: + switch (event[2]) { + case 0x00: + pr_debug("%s: Stimpad disable event" + " = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + case 0x01: + pr_debug("%s: Stimpad enable event" + " = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + case 0x02: + pr_debug("%s: Stimpad disable by signature invalid" + " = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + case 0x03: + pr_debug("%s: Stimpad disable by nodes count invalid" + " raw frame = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + default: + pr_debug("%s: Unknown stimpad status = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + } + break; + + case EVT_TYPE_STATUS_NO_TOUCH: + pr_info("%s: No Touch Status Event = %02X %02X" + " %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], event[5], + event[6], event[7]); + break; + + case EVT_TYPE_STATUS_IDLE: + pr_info("%s: Idle Status Event = %02X %02X" + " %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], event[5], + event[6], event[7]); + break; + + case EVT_TYPE_STATUS_PALM_TOUCH: + switch (event[2]) { + case 0x01: + pr_info("%s: Palm block entry event" + " = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + case 0x02: + pr_info("%s: Palm block release event" + " = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + default: + pr_info("%s: Unknown palm touch status = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + } + break; + + case EVT_TYPE_STATUS_GRIP_TOUCH: + grid_touch_status = (event[2] & 0xF0) >> 4; + switch (grid_touch_status) { + case 0x01: + pr_info("%s: Grip Touch entry event" + " = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + case 0x02: + pr_info("%s: Grip Touch release event" + " = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + default: + pr_info("%s: Unknown grip touch status = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + } + break; + + case EVT_TYPE_STATUS_GOLDEN_RAW_VAL: + switch (event[2]) { + case 0x01: + pr_info("%s: Golden Raw Validation Pass" + " = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + case 0x02: + pr_info("%s: Golden Raw Validation Fail" + " = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + + default: + pr_info("%s: Unknown golden raw validation status = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + } + break; + + case EVT_TYPE_STATUS_GOLDEN_RAW_ERR: + pr_info("%s: Golden Raw Data Abnormal" + " = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + break; + +#ifdef SUPPORT_PROX_PALM + case EVT_TYPE_STATUS_PROX_PALM: + switch (event[2]) { + case 0x00: + pr_info("%s: Proximity palm release event" + " = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + info->prox_palm_status = 0; + sysfs_notify(&info->dev->kobj, NULL, dev_attr_prox_palm_status.attr.name); + break; + + case 0x01: + pr_info("%s: Proximity palm entry event" + " = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + info->prox_palm_status = 1; + sysfs_notify(&info->dev->kobj, NULL, dev_attr_prox_palm_status.attr.name); + break; + + default: + pr_info("%s: Unknown proximity palm status = %02X %02X %02X %02X %02X %02X\n", + __func__, event[2], event[3], event[4], + event[5], event[6], event[7]); + } + break; +#endif + default: + pr_info("%s: Received unknown status event = %02X %02X %02X %02X %02X %02X %02X %02X\n", + __func__, event[0], event[1], event[2], event[3], + event[4], event[5], event[6], event[7]); + break; + } + return false; +} + + +/* key events reported in the user report */ +#ifdef PHONE_KEY +/* TODO: the customer should handle the events coming from the keys according + * his needs + * (this is just an sample code that report the click of a button after a + * press->release action) */ +/** + * Event handler for status events (EVT_TYPE_USER_KEY) + * Handle keys update events, the third byte of the event is a bitmask, + * if the bit set means that the corresponding key is pressed. + */ +static void fts_key_event_handler(struct fts_ts_info *info, + unsigned char *event) +{ + /* int value; */ + pr_info("%s: Received event %02X %02X %02X %02X %02X %02X %02X %02X\n", + __func__, event[0], event[1], event[2], event[3], event[4], + event[5], event[6], event[7]); + + if (event[0] == EVT_ID_USER_REPORT && event[1] == EVT_TYPE_USER_KEY) { + /* event[2] contain the bitmask of the keys that are actually + * pressed */ + + if ((event[2] & FTS_KEY_0) == 0 && (key_mask & FTS_KEY_0) > 0) { + pr_info("%s: Button HOME pressed and released!\n", + __func__); + fts_input_report_key(info, KEY_HOMEPAGE); + } + + if ((event[2] & FTS_KEY_1) == 0 && (key_mask & FTS_KEY_1) > 0) { + pr_info("%s: Button Back pressed and released!\n", + __func__); + fts_input_report_key(info, KEY_BACK); + } + + if ((event[2] & FTS_KEY_2) == 0 && (key_mask & FTS_KEY_2) > 0) { + pr_info("%s: Button Menu pressed!\n", __func__); + fts_input_report_key(info, KEY_MENU); + } + + key_mask = event[2]; + } else + pr_err("%s: Invalid event passed as argument!\n", __func__); +} +#endif + +/* gesture event must be handled in the user event handler */ +#ifdef GESTURE_MODE +/* TODO: Customer should implement their own actions in respond of a gesture + * event. + * This is an example that simply print the gesture received and simulate + * the click on a different button for each gesture. */ +/** + * Event handler for gesture events (EVT_TYPE_USER_GESTURE) + * Handle gesture events and simulate the click on a different button + * for any gesture detected (@link gesture_opt Gesture IDs @endlink) + */ +static void fts_gesture_event_handler(struct fts_ts_info *info, unsigned + char *event) +{ + int value; + int needCoords = 0; + + pr_info("gesture event data: %02X %02X %02X %02X %02X %02X %02X %02X\n", + event[0], event[1], event[2], event[3], event[4], + event[5], event[6], event[7]); + + if (event[0] == EVT_ID_USER_REPORT && event[1] == + EVT_TYPE_USER_GESTURE) { + needCoords = 1; + /* default read the coordinates for all gestures excluding + * double tap */ + + switch (event[2]) { + case GEST_ID_DBLTAP: + value = KEY_WAKEUP; + pr_info("%s: double tap !\n", __func__); + needCoords = 0; + break; + + case GEST_ID_AT: + value = KEY_WWW; + pr_info("%s: @ !\n", __func__); + break; + + case GEST_ID_C: + value = KEY_C; + pr_info("%s: C !\n", __func__); + break; + + case GEST_ID_E: + value = KEY_E; + pr_info("%s: e !\n", __func__); + break; + + case GEST_ID_F: + value = KEY_F; + pr_info("%s: F !\n", __func__); + break; + + case GEST_ID_L: + value = KEY_L; + pr_info("%s: L !\n", __func__); + break; + + case GEST_ID_M: + value = KEY_M; + pr_info("%s: M !\n", __func__); + break; + + case GEST_ID_O: + value = KEY_O; + pr_info("%s: O !\n", __func__); + break; + + case GEST_ID_S: + value = KEY_S; + pr_info("%s: S !\n", __func__); + break; + + case GEST_ID_V: + value = KEY_V; + pr_info("%s: V !\n", __func__); + break; + + case GEST_ID_W: + value = KEY_W; + pr_info("%s: W !\n", __func__); + break; + + case GEST_ID_Z: + value = KEY_Z; + pr_info("%s: Z !\n", __func__); + break; + + case GEST_ID_RIGHT_1F: + value = KEY_RIGHT; + pr_info("%s: -> !\n", __func__); + break; + + case GEST_ID_LEFT_1F: + value = KEY_LEFT; + pr_info("%s: <- !\n", __func__); + break; + + case GEST_ID_UP_1F: + value = KEY_UP; + pr_info("%s: UP !\n", __func__); + break; + + case GEST_ID_DOWN_1F: + value = KEY_DOWN; + pr_info("%s: DOWN !\n", __func__); + break; + + case GEST_ID_CARET: + value = KEY_APOSTROPHE; + pr_info("%s: ^ !\n", __func__); + break; + + case GEST_ID_LEFTBRACE: + value = KEY_LEFTBRACE; + pr_info("%s: < !\n", __func__); + break; + + case GEST_ID_RIGHTBRACE: + value = KEY_RIGHTBRACE; + pr_info("%s: > !\n", __func__); + break; + + default: + pr_err("%s: No valid GestureID!\n", __func__); + goto gesture_done; + } + + if (needCoords == 1) + readGestureCoords(event); + + fts_input_report_key(info, value); + +gesture_done: + return; + } else + pr_err("%s: Invalid event passed as argument!\n", __func__); +} +#endif + + +/** + * Event handler for user report events (EVT_ID_USER_REPORT) + * Handle user events reported by the FW due to some interaction triggered + * by an external user (press keys, perform gestures, etc.) + */ +static bool fts_user_report_event_handler(struct fts_ts_info *info, unsigned + char *event) +{ + switch (event[1]) { +#ifdef PHONE_KEY + case EVT_TYPE_USER_KEY: + fts_key_event_handler(info, event); + break; +#endif + + case EVT_TYPE_USER_PROXIMITY: + if (event[2] == 0) + pr_err("%s No proximity!\n", __func__); + else + pr_err("%s Proximity Detected!\n", __func__); + break; + +#ifdef GESTURE_MODE + case EVT_TYPE_USER_GESTURE: + fts_gesture_event_handler(info, event); + break; +#endif + default: + pr_err("%s: Received unhandled user report event = %02X %02X %02X %02X %02X %02X %02X %02X\n", + __func__, event[0], event[1], event[2], event[3], + event[4], event[5], event[6], event[7]); + break; + } + return false; +} + +#ifdef TOUCHSCREEN_HEATMAP +static void heatmap_enable(void) +{ + u8 command[] = {FTS_CMD_SYSTEM, SYS_CMD_LOAD_DATA, + LOCAL_HEATMAP_MODE}; + pr_info("%s\n", __func__); + fts_write(command, ARRAY_SIZE(command)); +} + +static bool read_heatmap_raw(struct v4l2_heatmap *v4l2) +{ + struct fts_ts_info *info = + container_of(v4l2, struct fts_ts_info, v4l2); + unsigned int num_elements; + /* index for looping through the heatmap buffer read over the bus */ + unsigned int local_i; + + int result; + + /* old value of the counter, for comparison */ + static uint16_t counter; + + strength_t heatmap_value; + /* final position of the heatmap value in the full heatmap frame */ + unsigned int frame_i; + + int heatmap_x, heatmap_y; + int max_x = v4l2->format.width; + int max_y = v4l2->format.height; + + struct heatmap_report report = {0}; + + if (info->heatmap_mode == FTS_HEATMAP_PARTIAL) { + result = fts_writeReadU8UX(FTS_CMD_FRAMEBUFFER_R, BITS_16, + ADDR_FRAMEBUFFER, (uint8_t *)&report, + sizeof(report), DUMMY_FRAMEBUFFER); + if (result != OK) { + pr_err("%s: i2c read failed, fts_writeRead returned %i", + __func__, result); + return false; + } + if (report.mode != LOCAL_HEATMAP_MODE) { + pr_err("Touch IC not in local heatmap mode: %X %X %i", + report.prefix, report.mode, report.counter); + heatmap_enable(); + return false; + } + + le16_to_cpus(&report.counter); /* enforce little-endian order */ + if (report.counter == counter && counter != 0) { + /* + * We shouldn't make ordered comparisons because of + * potential overflow, but at least the value + * should have changed. If the value remains the same, + * but we are processing a new interrupt, + * this could indicate slowness in the interrupt + * handler. + */ + if (info->mf_state != FTS_MF_UNFILTERED) + pr_warn("Heatmap frame has stale counter value %i", + counter); + } + counter = report.counter; + num_elements = report.size_x * report.size_y; + if (num_elements > LOCAL_HEATMAP_WIDTH * LOCAL_HEATMAP_HEIGHT) { + pr_err("Unexpected heatmap size: %i x %i", + report.size_x, report.size_y); + return false; + } + + /* set all to zero, will only write to non-zero locations in + * the loop + */ + memset(v4l2->frame, 0, v4l2->format.sizeimage); + /* populate the data buffer, rearranging into final locations */ + for (local_i = 0; local_i < num_elements; local_i++) { + /* enforce little-endian order */ + le16_to_cpus(&report.data[local_i]); + heatmap_value = report.data[local_i]; + + if (heatmap_value == 0) { + /* Already set to zero. Nothing to do */ + continue; + } + + heatmap_x = report.offset_x + (local_i % report.size_x); + heatmap_y = report.offset_y + (local_i / report.size_x); + + if (heatmap_x < 0 || heatmap_x >= max_x || + heatmap_y < 0 || heatmap_y >= max_y) { + pr_err("Invalid x or y: (%i, %i), value=%i, ending loop\n", + heatmap_x, heatmap_y, heatmap_value); + return false; + } + frame_i = heatmap_y * max_x + heatmap_x; + v4l2->frame[frame_i] = heatmap_value; + } + } else if (info->heatmap_mode == FTS_HEATMAP_FULL) { + MutualSenseFrame ms_frame = { 0 }; + uint32_t frame_index = 0, x, y; + + result = getMSFrame3(MS_STRENGTH, &ms_frame); + if (result <= 0) { + pr_err("getMSFrame3 failed with result=0x%08X.\n", + result); + return false; + } + + for (y = 0; y < max_y; y++) { + for (x = 0; x < max_x; x++) { + /* Rotate frame counter-clockwise and invert + * if necessary. + */ + if (!info->board->sensor_inverted) { + heatmap_value = + (strength_t)ms_frame.node_data[ + ((max_x-1) - x) * max_y + y]; + } else { + heatmap_value = + (strength_t)ms_frame.node_data[ + ((max_x-1) - x) * max_y + + ((max_y-1) - y)]; + } + v4l2->frame[frame_index++] = heatmap_value; + } + } + + kfree(ms_frame.node_data); + } else + return false; + + return true; +} +#endif + +/* Update a state machine used to toggle control of the touch IC's motion + * filter. + */ +static int update_motion_filter(struct fts_ts_info *info) +{ + /* Motion filter timeout, in milliseconds */ + const u32 mf_timeout_ms = 500; + u8 next_state; + u8 touches = hweight32(info->touch_id); /* Count the active touches */ + + if (info->use_default_mf) + return 0; + + /* Determine the next filter state. The motion filter is enabled by + * default and it is disabled while a single finger is touching the + * screen. If another finger is touched down or if a timeout expires, + * the motion filter is reenabled and remains enabled until all fingers + * are lifted. + */ + next_state = info->mf_state; + switch (info->mf_state) { + case FTS_MF_FILTERED: + if (touches == 1) { + next_state = FTS_MF_UNFILTERED; + info->mf_downtime = ktime_get(); + } + break; + case FTS_MF_UNFILTERED: + if (touches == 0) { + next_state = FTS_MF_FILTERED; + } else if (touches > 1 || + ktime_after(ktime_get(), + ktime_add_ms(info->mf_downtime, + mf_timeout_ms))) { + next_state = FTS_MF_FILTERED_LOCKED; + } + break; + case FTS_MF_FILTERED_LOCKED: + if (touches == 0) { + next_state = FTS_MF_FILTERED; + } + break; + } + + /* Send command to update filter state */ + if ((next_state == FTS_MF_UNFILTERED) != + (info->mf_state == FTS_MF_UNFILTERED)) { + u8 cmd[3] = {0xC0, 0x05, 0x00}; + pr_debug("%s: setting motion filter = %s.\n", __func__, + (next_state == FTS_MF_UNFILTERED) ? "false" : "true"); + cmd[2] = (next_state == FTS_MF_UNFILTERED) ? 0x01 : 0x00; + fts_write(cmd, sizeof(cmd)); + } + info->mf_state = next_state; + + return 0; +} + +/** + * Bottom Half Interrupt Handler function + * This handler is called each time there is at least one new event in the FIFO + * and the interrupt pin of the IC goes low. It will read all the events from + * the FIFO and dispatch them to the proper event handler according the event + * ID + */ +static irqreturn_t fts_interrupt_handler(int irq, void *handle) +{ + struct fts_ts_info *info = handle; + int error = 0, count = 0; + unsigned char regAdd = FIFO_CMD_READALL; + unsigned char data[FIFO_EVENT_SIZE * FIFO_DEPTH]; + unsigned char eventId; + const unsigned char EVENTS_REMAINING_POS = 7; + const unsigned char EVENTS_REMAINING_MASK = 0x1F; + unsigned char events_remaining = 0; + unsigned char *evt_data; + bool processed_pointer_event = false; + + /* It is possible that interrupts were disabled while the handler is + * executing, before acquiring the mutex. If so, simply return. + */ + if (fts_set_bus_ref(info, FTS_BUS_REF_IRQ, true) < 0) { + fts_set_bus_ref(info, FTS_BUS_REF_IRQ, false); + return IRQ_HANDLED; + } + + /* prevent CPU from entering deep sleep */ + pm_qos_update_request(&info->pm_qos_req, 100); + + __pm_wakeup_event(info->wakesrc, jiffies_to_msecs(HZ)); + + /* Read the first FIFO event and the number of events remaining */ + error = fts_writeReadU8UX(regAdd, 0, 0, data, FIFO_EVENT_SIZE, + DUMMY_FIFO); + events_remaining = data[EVENTS_REMAINING_POS] & EVENTS_REMAINING_MASK; + events_remaining = (events_remaining > FIFO_DEPTH - 1) ? + FIFO_DEPTH - 1 : events_remaining; + + /* Drain the rest of the FIFO, up to 31 events */ + if (error == OK && events_remaining > 0) { + error = fts_writeReadU8UX(regAdd, 0, 0, &data[FIFO_EVENT_SIZE], + FIFO_EVENT_SIZE * events_remaining, + DUMMY_FIFO); + } + if (error != OK) { + pr_err("Error (%08X) while reading from FIFO in fts_event_handler\n", + error); + } else { + for (count = 0; count < events_remaining + 1; count++) { + evt_data = &data[count * FIFO_EVENT_SIZE]; + + if (evt_data[0] == EVT_ID_NOEVENT) + break; + + eventId = evt_data[0] >> 4; + + /* Ensure event ID is within bounds */ + if (eventId < NUM_EVT_ID) { + processed_pointer_event = info->event_dispatch_table[eventId]( + info, evt_data); + } + } + } + + if (info->touch_id == 0) + input_report_key(info->input_dev, BTN_TOUCH, 0); + + input_sync(info->input_dev); + +#ifdef TOUCHSCREEN_HEATMAP + if (processed_pointer_event) + heatmap_read(&info->v4l2, ktime_to_ns(info->timestamp)); +#endif + + /* Disable the firmware motion filter during single touch */ + update_motion_filter(info); + + pm_qos_update_request(&info->pm_qos_req, PM_QOS_DEFAULT_VALUE); + fts_set_bus_ref(info, FTS_BUS_REF_IRQ, false); + return IRQ_HANDLED; +} + +/* + * Read the display panel's extinfo from the display driver. + * + * The display driver finds out the extinfo is available for a panel based on + * the device tree, but cannot read the extinfo itself until the DSI bus is + * initialized. Since the extinfo is not guaranteed to be available at the time + * the touch driver is probed or even when the automatic firmware update work is + * run. The display driver's API for reading extinfo does allow a client to + * query the size of the expected data and whether it is available. + * + * This function retrieves the extinfo from the display driver with an optional + * retry period to poll the display driver before giving up. + * + * @return 0 if success, -EBUSY if timeout + */ +static int fts_read_panel_extinfo(struct fts_ts_info *info, int wait_seconds) +{ + const int RETRIES_PER_S = 4; + const int MS_PER_RETRY = 1000 / RETRIES_PER_S; + ssize_t len = -EBUSY; + int retries = (wait_seconds <= 0) ? 0 : wait_seconds * RETRIES_PER_S; + int ret = 0; + + /* Was extinfo previously retrieved? */ + if (info->extinfo.is_read) + return 0; + + /* Extinfo should not be retrieved if the driver was unable to identify + * the panel via its panelmap. Consider the extinfo zero-length. + */ + if (!info->board->panel) { + info->extinfo.is_read = true; + info->extinfo.size = 0; + return 0; + } + + /* Obtain buffer size */ + len = dsi_panel_read_vendor_extinfo(info->board->panel, NULL, 0); + if (len == 0) { + /* No extinfo to be consumed */ + info->extinfo.size = 0; + info->extinfo.is_read = true; + return 0; + } else if (len < 0) { + ret = len; + pr_err("%s: dsi_panel_read_vendor_extinfo returned unexpected error = %d.\n", + __func__, ret); + goto error; + } else { + info->extinfo.data = kzalloc(len, GFP_KERNEL); + if (!info->extinfo.data) { + pr_err("%s: failed to allocate extinfo. len=%d.\n", + __func__, len); + ret = -ENOMEM; + goto error; + } + info->extinfo.size = len; + } + + /* Read vendor extinfo data */ + do { + len = dsi_panel_read_vendor_extinfo(info->board->panel, + info->extinfo.data, + info->extinfo.size); + if (len == -EBUSY) { + pr_debug("%s: sleeping %dms.\n", __func__, + MS_PER_RETRY); + msleep(MS_PER_RETRY); + } else if (len == info->extinfo.size) { + info->extinfo.is_read = true; + pr_debug("%s: Ultimately waited %d seconds.\n", + __func__, + wait_seconds - (retries / RETRIES_PER_S)); + return 0; + } else { + pr_err("%s: dsi_panel_read_vendor_extinfo returned error = %d\n", + __func__, len); + ret = len; + goto error; + } + } while (--retries > 0); + + /* Time out after retrying for wait_seconds */ + pr_err("%s: Timed out after waiting %d seconds.\n", __func__, + wait_seconds); + ret = -EBUSY; + +error: + kfree(info->extinfo.data); + info->extinfo.data = NULL; + info->extinfo.size = 0; + info->extinfo.is_read = false; + return ret; +} + +/* + * Determine the display panel based on the device tree and any extinfo read + * from the panel. + * + * Basic panel detection (e.g., unique part numbers) is performed by polling for + * connected drm_panels. Next, an override table from the device tree is used to + * parse the panel's extended info to distinguish between panel varients that + * require different firmware. + */ +static int fts_identify_panel(struct fts_ts_info *info) +{ + /* Formatting of EXTINFO rows provided in the device trees */ + const int EXTINFO_ROW_ELEMS = 5; + const int EXTINFO_ROW_SIZE = EXTINFO_ROW_ELEMS * sizeof(u32); + + struct device_node *np = info->dev->of_node; + u32 panel_index = info->board->initial_panel_index; + int extinfo_rows; + u32 filter_panel_index, filter_extinfo_index, filter_extinfo_mask; + u32 filter_extinfo_value, filter_extinfo_fw; + const char *name; + u32 inverted; + int i; + int ret = 0; + + if (!info->extinfo.is_read) { + /* Extinfo was not read. Attempt one read before aborting */ + ret = fts_read_panel_extinfo(info, 0); + if (ret < 0) { + pr_err("%s: fts_read_panel_extinfo failed with ret=%d.\n", + __func__, ret); + goto get_panel_info_failed; + } + } + + /* Read the extinfo override table to determine if there are is any + * reason to select a different firmware for the panel. + */ + if (of_property_read_bool(np, "st,extinfo_override_table")) { + extinfo_rows = of_property_count_elems_of_size( + np, "st,extinfo_override_table", + EXTINFO_ROW_SIZE); + + for (i = 0; i < extinfo_rows; i++) { + of_property_read_u32_index( + np, "st,extinfo_override_table", + i * EXTINFO_ROW_ELEMS + 0, + &filter_panel_index); + + of_property_read_u32_index( + np, "st,extinfo_override_table", + i * EXTINFO_ROW_ELEMS + 1, + &filter_extinfo_index); + + of_property_read_u32_index( + np, "st,extinfo_override_table", + i * EXTINFO_ROW_ELEMS + 2, + &filter_extinfo_mask); + + of_property_read_u32_index( + np, "st,extinfo_override_table", + i * EXTINFO_ROW_ELEMS + 3, + &filter_extinfo_value); + + of_property_read_u32_index( + np, "st,extinfo_override_table", + i * EXTINFO_ROW_ELEMS + 4, + &filter_extinfo_fw); + + if (panel_index != filter_panel_index) + continue; + else if (filter_extinfo_index >= info->extinfo.size) { + pr_err("%s: extinfo index is out of bounds (%d >= %d) in row %d of extinfo_override_table.\n", + __func__, filter_extinfo_index, + info->extinfo.size, i); + continue; + } else if ((info->extinfo.data[filter_extinfo_index] & + filter_extinfo_mask) == + filter_extinfo_value) { + /* Override the panel_index as specified in the + * override table. + */ + panel_index = filter_extinfo_fw; + pr_info("%s: Overriding with row=%d, panel_index=%d.\n", + __func__, i, panel_index); + break; + } + } + } else { + pr_err("%s: of_property_read_bool(np, \"st,extinfo_override_table\") failed.\n", + __func__); + } + + //--------------------------------------------------------------------- + // Read firmware name, limits file name, and sensor inversion based on + // the final panel index. In order to handle the case where the DRM + // panel was not detected from the list in the device tree, fall back to + // using predefined FW and limits paths hardcoded into the driver. + // -------------------------------------------------------------------- +get_panel_info_failed: + name = NULL; + if (info->board->panel) + of_property_read_string_index(np, "st,firmware_names", + panel_index, &name); + if (!name) + info->board->fw_name = PATH_FILE_FW; + else + info->board->fw_name = name; + pr_info("firmware name = %s\n", info->board->fw_name); + + name = NULL; + if (info->board->panel) + of_property_read_string_index(np, "st,limits_names", + panel_index, &name); + if (!name) + info->board->limits_name = LIMITS_FILE; + else + info->board->limits_name = name; + pr_info("limits name = %s\n", info->board->limits_name); + + inverted = 0; + if (info->board->panel) + of_property_read_u32_index(np, "st,sensor_inverted", + panel_index, &inverted); + info->board->sensor_inverted = (inverted != 0); + pr_info("Sensor inverted = %u\n", inverted); + + return ret; +} + +/** + * Implement the fw update and initialization flow of the IC that should + * be executed at every boot up. The function perform a fw update of the + * IC in case of crc error or a new fw version and then understand if the + * IC need to be re-initialized again. + * + * @return OK if success or an error code which specify the type of error + * encountered + */ +static int fts_fw_update(struct fts_ts_info *info) +{ + u8 error_to_search[4] = { EVT_TYPE_ERROR_CRC_CX_HEAD, + EVT_TYPE_ERROR_CRC_CX, + EVT_TYPE_ERROR_CRC_CX_SUB_HEAD, + EVT_TYPE_ERROR_CRC_CX_SUB }; + int ret; + int error = 0; + int init_type = NO_INIT; + int index; + int prop_len = 0; + struct device_node *np = info->dev->of_node; + +#if defined(PRE_SAVED_METHOD) || defined(COMPUTE_INIT_METHOD) + int keep_cx = 1; +#else + int keep_cx = 0; +#endif + + /* Read extinfo from display driver. Wait for up to ten seconds if + * there is extinfo to read but is not yet available. + */ + ret = fts_read_panel_extinfo(info, 10); + if (ret < 0) + pr_err("%s: Failed or timed out during read of extinfo. ret=%d\n", + __func__, ret); + + /* Identify panel given extinfo that may have been received. */ + ret = fts_identify_panel(info); + if (ret < 0) { + pr_err("%s: Encountered error while identifying display panel. ret=%d\n", + __func__, ret); + goto out; + } + + pr_info("Fw Auto Update is starting...\n"); + + /* Check CRC status */ + ret = fts_crc_check(); + if (ret > OK) { + pr_err("%s: CRC Error or NO FW!\n", __func__); + info->reflash_fw = 1; + } else { + pr_info("%s: NO CRC Error or Impossible to read CRC register!\n", + __func__); + } + + if (of_property_read_bool(np, "st,force-pi-cfg-ver-map")) { + prop_len = of_property_count_u32_elems(np, + "st,force-pi-cfg-ver-map"); + info->board->force_pi_cfg_ver = devm_kzalloc(info->dev, + sizeof(u32) * prop_len, GFP_KERNEL); + if (info->board->force_pi_cfg_ver != NULL) { + for (index = 0; index < prop_len; index++) { + of_property_read_u32_index(np, + "st,force-pi-cfg-ver-map", + index, + &info->board->force_pi_cfg_ver[index]); + pr_info("%s: force PI config version: %04X", + __func__, + info->board->force_pi_cfg_ver[index]); + if(systemInfo.u16_cfgVer == + info->board->force_pi_cfg_ver[index]) { + pr_info("%s System config version %04X, do panel init", + __func__, systemInfo.u16_cfgVer); + init_type = SPECIAL_PANEL_INIT; + } + } + } else { + pr_err("%s: force_pi_cfg_ver is NULL", __func__); + } + } else { + pr_info("%s: of_property_read_bool(np, \"st,force-pi-cfg-ver-map\") failed.\n", + __func__); + } + + if (info->board->auto_fw_update) { + ret = flashProcedure(info->board->fw_name, info->reflash_fw, + keep_cx); + if ((ret & 0xF000000F) == ERROR_FILE_NOT_FOUND) { + pr_err("%s: firmware file not found. Bypassing update.\n", + __func__); + ret = 0; + goto out; + } else if ((ret & 0xFF000000) == ERROR_FLASH_PROCEDURE) { + pr_err("%s: firmware update failed; retrying. ERROR %08X\n", + __func__, ret); + /* Power cycle the touch IC */ + fts_chip_powercycle(info); + ret = flashProcedure(info->board->fw_name, + info->reflash_fw, keep_cx); + if ((ret & 0xFF000000) == ERROR_FLASH_PROCEDURE) { + pr_err("%s: firmware update failed again! ERROR %08X\n", + __func__, ret); + pr_err("Fw Auto Update Failed!\n"); + return ret; + } + } + info->reflash_fw = 0; + } + + pr_info("%s: Verifying if CX CRC Error...\n", __func__); + ret = fts_system_reset(); + if (ret >= OK) { + ret = pollForErrorType(error_to_search, 4); + if (ret < OK) { + pr_info("%s: No Cx CRC Error Found!\n", __func__); + pr_info("%s: Verifying if Panel CRC Error...\n", + __func__); + error_to_search[0] = EVT_TYPE_ERROR_CRC_PANEL_HEAD; + error_to_search[1] = EVT_TYPE_ERROR_CRC_PANEL; + ret = pollForErrorType(error_to_search, 2); + if (ret < OK) { + pr_info("%s: No Panel CRC Error Found!\n", + __func__); + } else { + pr_err("%s: Panel CRC Error FOUND! CRC ERROR = %02X\n", + __func__, ret); + init_type = SPECIAL_PANEL_INIT; + } + } else { + pr_err("%s: Cx CRC Error FOUND! CRC ERROR = %02X\n", + __func__, ret); + + /** This path of the code is used only in case there is + * a CRC error in code or config which not allow the fw + * to compute the CRC in the CX before + */ +#ifndef COMPUTE_INIT_METHOD + pr_info("%s: Try to recovery with CX in fw file...\n", + __func__); + ret = flashProcedure(info->board->fw_name, CRC_CX, 0); + pr_info("%s: Refresh panel init data", __func__); +#else + pr_info("%s: Select Full Panel Init...\n", __func__); + init_type = SPECIAL_FULL_PANEL_INIT; +#endif + } + } else { + /* Skip initialization because the real state is unknown */ + pr_err("%s: Error while executing system reset! ERROR %08X\n", + __func__, ret); + } + + if (init_type != SPECIAL_FULL_PANEL_INIT) { +#if defined(PRE_SAVED_METHOD) || defined(COMPUTE_INIT_METHOD) + if ((systemInfo.u8_cfgAfeVer != systemInfo.u8_cxAfeVer) +#ifdef COMPUTE_INIT_METHOD + || ((systemInfo.u8_mpFlag != MP_FLAG_BOOT) && + (systemInfo.u8_mpFlag != MP_FLAG_FACTORY)) +#endif + ) { + init_type = SPECIAL_FULL_PANEL_INIT; + pr_err("%s: Different CX AFE Ver: %02X != %02X or invalid MpFlag = %02X... Execute FULL Panel Init!\n", + __func__, systemInfo.u8_cfgAfeVer, + systemInfo.u8_cxAfeVer, systemInfo.u8_mpFlag); + } else +#endif + if (systemInfo.u8_cfgAfeVer != systemInfo.u8_panelCfgAfeVer) { + init_type = SPECIAL_PANEL_INIT; + pr_err("%s: Different Panel AFE Ver: %02X != %02X... Execute Panel Init!\n", + __func__, systemInfo.u8_cfgAfeVer, + systemInfo.u8_panelCfgAfeVer); + } + } + +out: + + if (init_type != NO_INIT) { /* initialization status not correct or + * after FW complete update, do + * initialization. + */ + error = fts_chip_initialization(info, init_type); + if (error < OK) { + pr_err("%s: Cannot initialize the chip ERROR %08X\n", + __func__, error); + } + + /* Reset after initialization */ + ret = fts_system_reset(); + if (ret < OK) { + pr_err("%s: Reset failed, ERROR %08X\n", __func__, + ret); + } + } + + error = fts_init_sensing(info); + if (error < OK) { + pr_err("Cannot initialize the hardware device ERROR %08X\n", + error); + } + + pr_err("Fw Update Finished! error = %08X\n", error); + return error; +} + +/** + * Function called by the delayed workthread executed after the probe in + * order to perform the fw update flow + * @see fts_fw_update() + */ +static void fts_fw_update_auto(struct work_struct *work) +{ + struct delayed_work *fwu_work = container_of(work, struct delayed_work, + work); + struct fts_ts_info *info = container_of(fwu_work, struct fts_ts_info, + fwu_work); + + fts_set_bus_ref(info, FTS_BUS_REF_FW_UPDATE, true); + fts_fw_update(info); + fts_set_bus_ref(info, FTS_BUS_REF_FW_UPDATE, false); +} + +/* TODO: define if need to do the full mp at the boot */ +/** + * Execute the initialization of the IC (supporting a retry mechanism), + * checking also the resulting data + * @see production_test_main() + */ +static int fts_chip_initialization(struct fts_ts_info *info, int init_type) +{ + int ret2 = 0; + int retry; + int initretrycnt = 0; +#ifdef COMPUTE_INIT_METHOD + const char *limits_file = info->board->limits_name; +#endif + + /* initialization error, retry initialization */ + for (retry = 0; retry < RETRY_INIT_BOOT; retry++) { +#ifndef COMPUTE_INIT_METHOD + ret2 = production_test_initialization(init_type); +#else + ret2 = production_test_main(limits_file, 1, init_type, &tests, + MP_FLAG_BOOT); +#endif + if (ret2 == OK) + break; + initretrycnt++; + pr_err("initialization cycle count = %04d - ERROR %08X\n", + initretrycnt, ret2); + fts_chip_powercycle(info); + } + + if (ret2 < OK) /* initialization error */ + pr_err("fts initialization failed %d times\n", RETRY_INIT_BOOT); + + return ret2; +} + + +static irqreturn_t fts_isr(int irq, void *handle) +{ + struct fts_ts_info *info = handle; + + info->timestamp = ktime_get(); + input_set_timestamp(info->input_dev, info->timestamp); + + return IRQ_WAKE_THREAD; +} + +/** + * Initialize the dispatch table with the event handlers for any possible event + * ID + * Set IRQ pin behavior (level triggered low) + * Register top half interrupt handler function. + * @see fts_interrupt_handler() + */ +static int fts_interrupt_install(struct fts_ts_info *info) +{ + int i, error = 0; + + info->event_dispatch_table = kzalloc(sizeof(event_dispatch_handler_t) * + NUM_EVT_ID, GFP_KERNEL); + + if (!info->event_dispatch_table) { + pr_err("OOM allocating event dispatch table\n"); + return -ENOMEM; + } + + for (i = 0; i < NUM_EVT_ID; i++) + info->event_dispatch_table[i] = fts_nop_event_handler; + + install_handler(info, ENTER_POINT, enter_pointer); + install_handler(info, LEAVE_POINT, leave_pointer); + install_handler(info, MOTION_POINT, motion_pointer); + install_handler(info, ERROR, error); + install_handler(info, CONTROLLER_READY, controller_ready); + install_handler(info, STATUS_UPDATE, status); + install_handler(info, USER_REPORT, user_report); + + /* disable interrupts in any case */ + error = fts_enableInterrupt(false); + if (error) { + return error; + } + + error = request_threaded_irq(info->client->irq, fts_isr, + fts_interrupt_handler, IRQF_ONESHOT | IRQF_TRIGGER_LOW, + FTS_TS_DRV_NAME, info); + info->irq_enabled = true; + + if (error) { + pr_err("Request irq failed\n"); + kfree(info->event_dispatch_table); + } + + return error; +} + +/** + * Clean the dispatch table and the free the IRQ. + * This function is called when the driver need to be removed + */ +static void fts_interrupt_uninstall(struct fts_ts_info *info) +{ + fts_enableInterrupt(false); + + kfree(info->event_dispatch_table); + + free_irq(info->client->irq, info); +} + +/**@}*/ + +/** + * This function try to attempt to communicate with the IC for the first time + * during the boot up process in order to read the necessary info for the + * following stages. + * The function execute a system reset, read fundamental info (system info) + * @return OK if success or an error code which specify the type of error + */ +static int fts_init(struct fts_ts_info *info) +{ + int error; + + + error = fts_system_reset(); + if (error < OK && isI2cError(error)) { + pr_err("Cannot reset the device! ERROR %08X\n", error); + return error; + } else { + if (error == (ERROR_TIMEOUT | ERROR_SYSTEM_RESET_FAIL)) { + pr_err("Setting default Sys INFO!\n"); + error = defaultSysInfo(0); + } else { + error = readSysInfo(0); /* system reset OK */ + if (error < OK) { + if (!isI2cError(error)) + error = OK; + pr_err("Cannot read Sys Info! ERROR %08X\n", + error); + } + } + } + + return error; +} + +/** + * Execute a power cycle in the IC, toggling the power lines (AVDD and DVDD) + * @param info pointer to fts_ts_info struct which contain information of the + * regulators + * @return 0 if success or another value if fail + */ +int fts_chip_powercycle(struct fts_ts_info *info) +{ + int error = 0; + + pr_info("%s: Power Cycle Starting...\n", __func__); + pr_info("%s: Disabling IRQ...\n", __func__); + /** if IRQ pin is short with DVDD a call to the ISR will triggered when + * the regulator is turned off if IRQ not disabled */ + fts_enableInterrupt(false); + + if (info->vdd_reg) { + error = regulator_disable(info->vdd_reg); + if (error < 0) + pr_err("%s: Failed to disable DVDD regulator\n", + __func__); + } + + if (info->avdd_reg) { + error = regulator_disable(info->avdd_reg); + if (error < 0) + pr_err("%s: Failed to disable AVDD regulator\n", + __func__); + } + + if (info->board->reset_gpio != GPIO_NOT_DEFINED) + gpio_set_value(info->board->reset_gpio, 0); + else + mdelay(300); + + /* in FTI power up first the digital and then the analog */ + if (info->vdd_reg) { + error = regulator_enable(info->vdd_reg); + if (error < 0) + pr_err("%s: Failed to enable DVDD regulator\n", + __func__); + } + + mdelay(1); + + if (info->avdd_reg) { + error = regulator_enable(info->avdd_reg); + if (error < 0) + pr_err("%s: Failed to enable AVDD regulator\n", + __func__); + } + + mdelay(5); /* time needed by the regulators for reaching the regime + * values */ + + + if (info->board->reset_gpio != GPIO_NOT_DEFINED) { + mdelay(10); /* time to wait before bring up the reset + * gpio after the power up of the regulators */ + gpio_set_value(info->board->reset_gpio, 1); + } + + release_all_touches(info); + + pr_info("%s: Power Cycle Finished! ERROR CODE = %08x\n", + __func__, error); + setSystemResetedUp(1); + setSystemResetedDown(1); + return error; +} + + +/** + * Complete the boot up process, initializing the sensing of the IC according + * to the current setting chosen by the host + * Register the notifier for the suspend/resume actions and the event handler + * @return OK if success or an error code which specify the type of error + */ +static int fts_init_sensing(struct fts_ts_info *info) +{ + int error = 0; + +#ifdef CONFIG_DRM + error |= drm_panel_notifier_register(info->board->panel, + &info->notifier); /* register the + * suspend/resume + * function */ +#endif + error |= fts_interrupt_install(info); /* register event handler */ + error |= fts_mode_handler(info, 0); /* enable the features and + * sensing */ + error |= fts_enableInterrupt(true); /* enable the interrupt */ + + if (error < OK) + pr_err("%s Init after Probe error (ERROR = %08X)\n", + __func__, error); + +#ifdef TOUCHSCREEN_HEATMAP + heatmap_enable(); +#endif + + return error; +} + +/* TODO: change this function according with the needs of customer in terms + * of feature to enable/disable */ + +/** + * @ingroup mode_section + * @{ + */ +/** + * The function handle the switching of the mode in the IC enabling/disabling + * the sensing and the features set from the host + * @param info pointer to fts_ts_info which contains info about the device and + * its hw setup + * @param force if 1, the enabling/disabling command will be send even + * if the feature was already enabled/disabled otherwise it will judge if + * the feature changed status or the IC had a system reset + * @return OK if success or an error code which specify the type of error + */ +static int fts_mode_handler(struct fts_ts_info *info, int force) +{ + int res = OK; + int ret = OK; + u8 settings[4] = { 0 }; + + /* disable irq wake because resuming from gesture mode */ + if (IS_POWER_MODE(info->mode, SCAN_MODE_LOW_POWER) && + (info->resume_bit == 1)) + disable_irq_wake(info->client->irq); + + info->mode = MODE_NOTHING; /* initialize the mode to nothing + * in order to be updated depending + * on the features enabled */ + + pr_debug("%s: Mode Handler starting...\n", __func__); + switch (info->resume_bit) { + case 0: /* screen down */ + pr_debug("%s: Screen OFF...\n", __func__); + /* do sense off in order to avoid the flooding of the fifo with + * touch events if someone is touching the panel during suspend + **/ + pr_info("%s: Sense OFF!\n", __func__); + /* for speed reason (no need to check echo in this case and + * interrupt can be enabled) */ + ret = setScanMode(SCAN_MODE_ACTIVE, 0x00); + res |= ret; /* to avoid warning unsused ret variable when a + * ll the features are disabled */ + +#ifdef GESTURE_MODE + if (info->gesture_enabled == 1) { + pr_info("%s: enter in gesture mode !\n", + __func__); + res = enterGestureMode(isSystemResettedDown()); + if (res >= OK) { + enable_irq_wake(info->client->irq); + fromIDtoMask(FEAT_SEL_GESTURE, + (u8 *)&info->mode, + sizeof(info->mode)); + MODE_LOW_POWER(info->mode, 0); + } else + pr_err("%s: enterGestureMode failed! ERROR %08X recovery in senseOff...\n", + __func__, res); + } +#endif + + setSystemResetedDown(0); + break; + + case 1: /* screen up */ + pr_debug("%s: Screen ON...\n", __func__); + +#ifdef GLOVE_MODE + if ((info->glove_enabled == FEAT_ENABLE && + isSystemResettedUp()) || force == 1) { + pr_info("%s: Glove Mode setting...\n", __func__); + settings[0] = info->glove_enabled; + /* required to satisfy also the disable case */ + ret = setFeatures(FEAT_SEL_GLOVE, settings, 1); + if (ret < OK) + pr_err("%s: error during setting GLOVE_MODE! ERROR %08X\n", + __func__, ret); + res |= ret; + + if (ret >= OK && info->glove_enabled == FEAT_ENABLE) { + fromIDtoMask(FEAT_SEL_GLOVE, (u8 *)&info->mode, + sizeof(info->mode)); + pr_info("%s: GLOVE_MODE Enabled!\n", __func__); + } else + pr_info("%s: GLOVE_MODE Disabled!\n", __func__); + } + +#endif + +#ifdef COVER_MODE + if ((info->cover_enabled == FEAT_ENABLE && + isSystemResettedUp()) || force == 1) { + pr_info("%s: Cover Mode setting...\n", __func__); + settings[0] = info->cover_enabled; + ret = setFeatures(FEAT_SEL_COVER, settings, 1); + if (ret < OK) + pr_err("%s: error during setting COVER_MODE! ERROR %08X\n", + __func__, ret); + res |= ret; + + if (ret >= OK && info->cover_enabled == FEAT_ENABLE) { + fromIDtoMask(FEAT_SEL_COVER, (u8 *)&info->mode, + sizeof(info->mode)); + pr_info("%s: COVER_MODE Enabled!\n", __func__); + } else + pr_info("%s: COVER_MODE Disabled!\n", __func__); + } +#endif +#ifdef CHARGER_MODE + if ((info->charger_enabled > 0 && isSystemResettedUp()) || + force == 1) { + pr_info("%s: Charger Mode setting...\n", __func__); + + settings[0] = info->charger_enabled; + ret = setFeatures(FEAT_SEL_CHARGER, settings, 1); + if (ret < OK) + pr_err("%s: error during setting CHARGER_MODE! ERROR %08X\n", + __func__, ret); + res |= ret; + + if (ret >= OK && info->charger_enabled == FEAT_ENABLE) { + fromIDtoMask(FEAT_SEL_CHARGER, + (u8 *)&info->mode, + sizeof(info->mode)); + pr_info("%s: CHARGER_MODE Enabled!\n", + __func__); + } else + pr_info("%s: CHARGER_MODE Disabled!\n", + __func__); + } +#endif + + +#ifdef GRIP_MODE + if ((info->grip_enabled == FEAT_ENABLE && + isSystemResettedUp()) || force == 1) { + pr_info("%s: Grip Mode setting...\n", __func__); + settings[0] = info->grip_enabled; + ret = setFeatures(FEAT_SEL_GRIP, settings, 1); + if (ret < OK) + pr_err("%s: error during setting GRIP_MODE! ERROR %08X\n", + __func__, ret); + res |= ret; + + if (ret >= OK && info->grip_enabled == FEAT_ENABLE) { + fromIDtoMask(FEAT_SEL_GRIP, (u8 *)&info->mode, + sizeof(info->mode)); + pr_info("%s: GRIP_MODE Enabled!\n", __func__); + } else + pr_info("%s: GRIP_MODE Disabled!\n", __func__); + } +#endif + /* If some selective scan want to be enabled can be done + * an or of the following options + */ + /* settings[0] = ACTIVE_MULTI_TOUCH | ACTIVE_KEY | */ + /* ACTIVE_HOVER | ACTIVE_PROXIMITY | */ + /* ACTIVE_FORCE; */ + settings[0] = 0xFF; /* enable all the possible scans mode + * supported by the config */ + pr_info("%s: Sense ON!\n", __func__); + res |= setScanMode(SCAN_MODE_ACTIVE, settings[0]); + info->mode |= (SCAN_MODE_ACTIVE << 24); + MODE_ACTIVE(info->mode, settings[0]); + + + setSystemResetedUp(0); + break; + + default: + pr_err("%s: invalid resume_bit value = %d! ERROR %08X\n", + __func__, info->resume_bit, ERROR_OP_NOT_ALLOW); + res = ERROR_OP_NOT_ALLOW; + } + + + pr_debug("%s: Mode Handler finished! res = %08X mode = %08X\n", + __func__, res, info->mode); + return res; +} + +/** + * Configure the switch GPIO to toggle bus master between AP and SLPI. + * gpio_value takes one of + * { FTS_SWITCH_GPIO_VALUE_SLPI_MASTER, FTS_SWITCH_GPIO_VALUE_AP_MASTER } + */ +static void fts_set_switch_gpio(struct fts_ts_info *info, int gpio_value) +{ + int retval; + unsigned int gpio = info->board->switch_gpio; + + if (!gpio_is_valid(gpio)) + return; + + pr_debug("%s: toggling i2c switch to %s\n", __func__, + gpio_value == FTS_SWITCH_GPIO_VALUE_AP_MASTER ? "AP" : "SLPI"); + + retval = gpio_direction_output(gpio, gpio_value); + if (retval < 0) + pr_err("%s: Failed to toggle switch_gpio, err = %d\n", + __func__, retval); +} + +/** + * Resume work function which perform a system reset, clean all the touches + * from the linux input system and prepare the ground for enabling the sensing + */ +static void fts_resume_work(struct work_struct *work) +{ + struct fts_ts_info *info; + + info = container_of(work, struct fts_ts_info, resume_work); + + if (!info->sensor_sleep) + return; + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_TBN) + if (info->tbn) + tbn_request_bus(info->tbn); +#endif + + fts_set_switch_gpio(info, FTS_SWITCH_GPIO_VALUE_AP_MASTER); + + fts_pinctrl_setup(info, true); + + __pm_wakeup_event(info->wakesrc, jiffies_to_msecs(HZ)); + + info->resume_bit = 1; + + fts_system_reset(); + + release_all_touches(info); + + fts_mode_handler(info, 0); + + info->sensor_sleep = false; + +#ifdef TOUCHSCREEN_HEATMAP + /* heatmap must be enabled after every chip reset (fts_system_reset) */ + heatmap_enable(); +#endif + + fts_enableInterrupt(true); + + complete_all(&info->bus_resumed); +} + +/** + * Suspend work function which clean all the touches from Linux input system + * and prepare the ground to disabling the sensing or enter in gesture mode + */ +static void fts_suspend_work(struct work_struct *work) +{ + struct fts_ts_info *info; + + info = container_of(work, struct fts_ts_info, suspend_work); + + if (info->sensor_sleep) + return; + + reinit_completion(&info->bus_resumed); + + __pm_stay_awake(info->wakesrc); + + info->resume_bit = 0; + + fts_mode_handler(info, 0); + + release_all_touches(info); + + fts_enableInterrupt(false); + + /* Flush any outstanding touch events */ + fts_system_reset(); + flushFIFO(); + + fts_pinctrl_setup(info, false); + + fts_set_switch_gpio(info, FTS_SWITCH_GPIO_VALUE_SLPI_MASTER); + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_TBN) + if (info->tbn) + tbn_release_bus(info->tbn); +#endif + + info->sensor_sleep = true; + __pm_relax(info->wakesrc); +} +/** @}*/ + + +static void fts_aggregate_bus_state(struct fts_ts_info *info) +{ + pr_debug("%s: bus_refmask = 0x%02X.\n", __func__, + info->bus_refmask); + + /* Complete or cancel any outstanding transitions */ + cancel_work_sync(&info->suspend_work); + cancel_work_sync(&info->resume_work); + + if ((info->bus_refmask == 0 && info->sensor_sleep) || + (info->bus_refmask != 0 && !info->sensor_sleep)) + return; + + if (info->bus_refmask == 0) + queue_work(info->event_wq, &info->suspend_work); + else + queue_work(info->event_wq, &info->resume_work); +} + +int fts_set_bus_ref(struct fts_ts_info *info, u16 ref, bool enable) +{ + int result = OK; + + mutex_lock(&info->bus_mutex); + + if ((enable && (info->bus_refmask & ref)) || + (!enable && !(info->bus_refmask & ref))) { + pr_debug("%s: reference is unexpectedly set: mask=0x%04X, ref=0x%04X, enable=%d.\n", + __func__, info->bus_refmask, ref, enable); + mutex_unlock(&info->bus_mutex); + return ERROR_OP_NOT_ALLOW; + } + + if (enable) { + /* IRQs can only keep the bus active. IRQs received while the + * bus is transferred to SLPI should be ignored. + */ + if (ref == FTS_BUS_REF_IRQ && info->bus_refmask == 0) + result = ERROR_OP_NOT_ALLOW; + else + info->bus_refmask |= ref; + } else + info->bus_refmask &= ~ref; + fts_aggregate_bus_state(info); + + mutex_unlock(&info->bus_mutex); + + /* When triggering a wake, wait up to one second to resume. SCREEN_ON + * and IRQ references do not need to wait. + */ + if (enable && ref != FTS_BUS_REF_SCREEN_ON && ref != FTS_BUS_REF_IRQ) { + wait_for_completion_timeout(&info->bus_resumed, HZ); + if (info->sensor_sleep) { + pr_err("%s: Failed to wake the touch bus: mask=0x%04X, ref=0x%04X, enable=%d.\n", + __func__, info->bus_refmask, ref, enable); + result = ERROR_TIMEOUT; + } + } + + return result; +} + +/** + * Callback function used to detect the suspend/resume events generated by + * clicking the power button. + * This function schedule a suspend or resume work according to the event + * received. + */ +#ifdef CONFIG_DRM +static int fts_screen_state_chg_callback(struct notifier_block *nb, + unsigned long val, void *data) +{ + struct fts_ts_info *info = container_of(nb, struct fts_ts_info, + notifier); + struct drm_panel_notifier *evdata = data; + unsigned int blank; + + if (val != DRM_PANEL_EVENT_BLANK && val != DRM_PANEL_EARLY_EVENT_BLANK) + return NOTIFY_DONE; + + if (!info || !evdata || !evdata->data) { + pr_info("%s: Bad fts notifier call!\n", __func__); + return NOTIFY_DONE; + } + + pr_debug("%s: fts notifier begin!\n", __func__); + + blank = *(int *) (evdata->data); + switch (blank) { + case DRM_PANEL_BLANK_POWERDOWN: + case DRM_PANEL_BLANK_LP: + if (val == DRM_PANEL_EARLY_EVENT_BLANK) { + pr_debug("%s: BLANK\n", __func__); +#ifdef SUPPORT_PROX_PALM + if (info->audio_status) + enable_prox_palm_only_mode(true); +#endif + fts_set_bus_ref(info, FTS_BUS_REF_SCREEN_ON, false); +#ifdef SUPPORT_PROX_PALM + release_all_touches(info); +#endif + } + break; + case DRM_PANEL_BLANK_UNBLANK: + if (val == DRM_PANEL_EVENT_BLANK) { + pr_debug("%s: UNBLANK\n", __func__); +#ifdef SUPPORT_PROX_PALM + if (info->audio_status) + enable_prox_palm_only_mode(false); +#endif + fts_set_bus_ref(info, FTS_BUS_REF_SCREEN_ON, true); +#ifdef SUPPORT_PROX_PALM + release_all_touches(info); +#endif + } + break; + } + +#ifdef DYNAMIC_REFRESH_RATE + if (info->display_refresh_rate != evdata->refresh_rate) { + info->display_refresh_rate = evdata->refresh_rate; + if (gpio_is_valid(info->board->disp_rate_gpio)) + gpio_set_value(info->board->disp_rate_gpio, + (info->display_refresh_rate == 90)); + pr_debug("Refresh rate changed to %d Hz.\n", + info->display_refresh_rate); + } +#endif + + return NOTIFY_OK; +} + +static struct notifier_block fts_noti_block = { + .notifier_call = fts_screen_state_chg_callback, +}; +#endif + +/** + * From the name of the power regulator get/put the actual regulator structs + * (copying their references into fts_ts_info variable) + * @param info pointer to fts_ts_info which contains info about the device and + * its hw setup + * @param get if 1, the regulators are get otherwise they are put (released) + * back to the system + * @return OK if success or an error code which specify the type of error + */ +static int fts_get_reg(struct fts_ts_info *info, bool get) +{ + int retval; + const struct fts_hw_platform_data *bdata = info->board; + + if (!get) { + retval = 0; + goto regulator_put; + } + + if ((bdata->vdd_reg_name != NULL) && (*bdata->vdd_reg_name != 0)) { + info->vdd_reg = regulator_get(info->dev, bdata->vdd_reg_name); + if (IS_ERR(info->vdd_reg)) { + pr_err("%s: Failed to get power regulator\n", __func__); + retval = PTR_ERR(info->vdd_reg); + goto regulator_put; + } + } + + if ((bdata->avdd_reg_name != NULL) && (*bdata->avdd_reg_name != 0)) { + info->avdd_reg = regulator_get(info->dev, bdata->avdd_reg_name); + if (IS_ERR(info->avdd_reg)) { + pr_err("%s: Failed to get bus pullup regulator\n", + __func__); + retval = PTR_ERR(info->avdd_reg); + goto regulator_put; + } + } + + return OK; + +regulator_put: + if (info->vdd_reg) { + regulator_put(info->vdd_reg); + info->vdd_reg = NULL; + } + + if (info->avdd_reg) { + regulator_put(info->avdd_reg); + info->avdd_reg = NULL; + } + + return retval; +} + + +/** + * Enable or disable the power regulators + * @param info pointer to fts_ts_info which contains info about the device and + * its hw setup + * @param enable if 1, the power regulators are turned on otherwise they are + * turned off + * @return OK if success or an error code which specify the type of error + */ +static int fts_enable_reg(struct fts_ts_info *info, bool enable) +{ + int retval; + + if (!enable) { + retval = 0; + goto disable_pwr_reg; + } + + if (info->vdd_reg) { + retval = regulator_enable(info->vdd_reg); + if (retval < 0) { + pr_err("%s: Failed to enable bus regulator\n", + __func__); + goto exit; + } + } + + if (info->avdd_reg) { + retval = regulator_enable(info->avdd_reg); + if (retval < 0) { + pr_err("%s: Failed to enable power regulator\n", + __func__); + goto disable_bus_reg; + } + } + + return OK; + +disable_pwr_reg: + if (info->avdd_reg) + regulator_disable(info->avdd_reg); + +disable_bus_reg: + if (info->vdd_reg) + regulator_disable(info->vdd_reg); + +exit: + return retval; +} + +/** + * Configure a GPIO according to the parameters + * @param gpio gpio number + * @param config if true, the gpio is set up otherwise it is free + * @param dir direction of the gpio, 0 = in, 1 = out + * @param state initial value (if the direction is in, this parameter is + * ignored) + * return error code + */ +static int fts_gpio_setup(int gpio, bool config, int dir, int state) +{ + int retval = 0; + unsigned char buf[16]; + + if (config) { + scnprintf(buf, sizeof(buf), "fts_gpio_%u\n", gpio); + + retval = gpio_request(gpio, buf); + if (retval) { + pr_err("%s: Failed to get gpio %d (code: %d)", + __func__, gpio, retval); + return retval; + } + + if (dir == 0) + retval = gpio_direction_input(gpio); + else + retval = gpio_direction_output(gpio, state); + if (retval) { + pr_err("%s: Failed to set gpio %d direction", + __func__, gpio); + return retval; + } + } else + gpio_free(gpio); + + return retval; +} + +/** + * Setup the IRQ and RESET (if present) gpios. + * If the Reset Gpio is present it will perform a cycle HIGH-LOW-HIGH in order + * to assure that the IC has been reset properly + */ +static int fts_set_gpio(struct fts_ts_info *info) +{ + int retval; + struct fts_hw_platform_data *bdata = + info->board; + + retval = fts_gpio_setup(bdata->irq_gpio, true, 0, 0); + if (retval < 0) { + pr_err("%s: Failed to configure irq GPIO\n", __func__); + goto err_gpio_irq; + } + + if (gpio_is_valid(bdata->switch_gpio)) { + retval = fts_gpio_setup(bdata->switch_gpio, true, 1, + FTS_SWITCH_GPIO_VALUE_AP_MASTER); + if (retval < 0) + pr_err("%s: Failed to configure I2C switch\n", + __func__); + } + +#ifdef DYNAMIC_REFRESH_RATE + if (gpio_is_valid(bdata->disp_rate_gpio)) { + retval = fts_gpio_setup(bdata->disp_rate_gpio, true, 1, + (info->display_refresh_rate == 90)); + if (retval < 0) + pr_err("%s: Failed to configure disp_rate_gpio\n", + __func__); + } +#endif + + if (bdata->reset_gpio >= 0) { + retval = fts_gpio_setup(bdata->reset_gpio, true, 1, 0); + if (retval < 0) { + pr_err("%s: Failed to configure reset GPIO\n", + __func__); + goto err_gpio_reset; + } + } + if (bdata->reset_gpio >= 0) { + gpio_set_value(bdata->reset_gpio, 0); + mdelay(10); + gpio_set_value(bdata->reset_gpio, 1); + } + + return OK; + +err_gpio_reset: + fts_gpio_setup(bdata->irq_gpio, false, 0, 0); + bdata->reset_gpio = GPIO_NOT_DEFINED; +err_gpio_irq: + return retval; +} + +/** Set pin state to active or suspend + * @param active 1 for active while 0 for suspend + */ +static void fts_pinctrl_setup(struct fts_ts_info *info, bool active) +{ + int retval; + + if (info->ts_pinctrl) { + /* + * Pinctrl setup is optional. + * If pinctrl is found, set pins to active/suspend state. + * Otherwise, go on without showing error messages. + */ + retval = pinctrl_select_state(info->ts_pinctrl, active ? + info->pinctrl_state_active : + info->pinctrl_state_suspend); + if (retval < 0) { + pr_err("Failed to select %s pinstate %d\n", active ? + PINCTRL_STATE_ACTIVE : PINCTRL_STATE_SUSPEND, + retval); + } + } else { + pr_warn("ts_pinctrl is NULL\n"); + } +} + +/** + * Get/put the touch pinctrl from the specific names. If pinctrl is used, the + * active and suspend pin control names and states are necessary. + * @param info pointer to fts_ts_info which contains info about the device and + * its hw setup + * @param get if 1, the pinctrl is get otherwise it is put (released) back to + * the system + * @return OK if success or an error code which specify the type of error + */ +static int fts_pinctrl_get(struct fts_ts_info *info, bool get) +{ + int retval; + + if (!get) { + retval = 0; + goto pinctrl_put; + } + + info->ts_pinctrl = devm_pinctrl_get(info->dev); + if (IS_ERR_OR_NULL(info->ts_pinctrl)) { + retval = PTR_ERR(info->ts_pinctrl); + pr_info("Target does not use pinctrl %d\n", retval); + goto err_pinctrl_get; + } + + info->pinctrl_state_active + = pinctrl_lookup_state(info->ts_pinctrl, PINCTRL_STATE_ACTIVE); + if (IS_ERR_OR_NULL(info->pinctrl_state_active)) { + retval = PTR_ERR(info->pinctrl_state_active); + pr_err("Can not lookup %s pinstate %d\n", + PINCTRL_STATE_ACTIVE, retval); + goto err_pinctrl_lookup; + } + + info->pinctrl_state_suspend + = pinctrl_lookup_state(info->ts_pinctrl, PINCTRL_STATE_SUSPEND); + if (IS_ERR_OR_NULL(info->pinctrl_state_suspend)) { + retval = PTR_ERR(info->pinctrl_state_suspend); + pr_err("Can not lookup %s pinstate %d\n", + PINCTRL_STATE_SUSPEND, retval); + goto err_pinctrl_lookup; + } + + info->pinctrl_state_release + = pinctrl_lookup_state(info->ts_pinctrl, PINCTRL_STATE_RELEASE); + if (IS_ERR_OR_NULL(info->pinctrl_state_release)) { + retval = PTR_ERR(info->pinctrl_state_release); + pr_warn("Can not lookup %s pinstate %d\n", + PINCTRL_STATE_RELEASE, retval); + } + + return OK; + +err_pinctrl_lookup: + devm_pinctrl_put(info->ts_pinctrl); +err_pinctrl_get: + info->ts_pinctrl = NULL; +pinctrl_put: + if (info->ts_pinctrl) { + if (IS_ERR_OR_NULL(info->pinctrl_state_release)) { + devm_pinctrl_put(info->ts_pinctrl); + info->ts_pinctrl = NULL; + } else { + if (pinctrl_select_state( + info->ts_pinctrl, + info->pinctrl_state_release)) + pr_warn("Failed to select release pinstate\n"); + } + } + return retval; +} + +/** + * Retrieve and parse the hw information from the device tree node defined in + * the system. + * the most important information to obtain are: IRQ and RESET gpio numbers, + * power regulator names + * In the device file node is possible to define additional optional + * information + * that can be parsed here. + */ +static int parse_dt(struct device *dev, struct fts_hw_platform_data *bdata) +{ + int retval; + int index; + struct of_phandle_args panelmap; + struct drm_panel *panel = NULL; + struct display_timing timing; + const char *name; + struct device_node *np = dev->of_node; + u32 coords[2]; + + if (of_property_read_bool(np, "st,panel_map")) { + for (index = 0 ;; index++) { + retval = of_parse_phandle_with_fixed_args(np, + "st,panel_map", + 1, + index, + &panelmap); + if (retval) + return -EPROBE_DEFER; + panel = of_drm_find_panel(panelmap.np); + of_node_put(panelmap.np); + if (!IS_ERR_OR_NULL(panel)) { + bdata->panel = panel; + bdata->initial_panel_index = panelmap.args[0]; + break; + } + } + } + + bdata->switch_gpio = of_get_named_gpio(np, "st,switch_gpio", 0); + pr_info("switch_gpio = %d\n", bdata->switch_gpio); + + bdata->irq_gpio = of_get_named_gpio_flags(np, "st,irq-gpio", 0, NULL); + pr_info("irq_gpio = %d\n", bdata->irq_gpio); + + retval = of_property_read_string(np, "st,regulator_dvdd", &name); + if (retval == -EINVAL) + bdata->vdd_reg_name = NULL; + else if (retval < 0) + return retval; + else { + bdata->vdd_reg_name = name; + pr_info("pwr_reg_name = %s\n", name); + } + + retval = of_property_read_string(np, "st,regulator_avdd", &name); + if (retval == -EINVAL) + bdata->avdd_reg_name = NULL; + else if (retval < 0) + return retval; + else { + bdata->avdd_reg_name = name; + pr_info("bus_reg_name = %s\n", name); + } + + if (of_property_read_bool(np, "st,reset-gpio")) { + bdata->reset_gpio = of_get_named_gpio_flags(np, + "st,reset-gpio", 0, + NULL); + pr_info("reset_gpio = %d\n", bdata->reset_gpio); + } else + bdata->reset_gpio = GPIO_NOT_DEFINED; + + if (of_property_read_bool(np, "st,disp-rate-gpio")) { + bdata->disp_rate_gpio = + of_get_named_gpio_flags(np, "st,disp-rate-gpio", 0, NULL); + pr_info("disp_rate_gpio = %d\n", bdata->disp_rate_gpio); + } else + bdata->disp_rate_gpio = GPIO_NOT_DEFINED; + + bdata->auto_fw_update = true; + if (of_property_read_bool(np, "st,disable-auto-fw-update")) { + bdata->auto_fw_update = false; + pr_info("Automatic firmware update disabled\n"); + } + +#ifdef TOUCHSCREEN_HEATMAP + bdata->heatmap_mode_full_init = false; + if (of_property_read_bool(np, "st,heatmap_mode_full")) { + bdata->heatmap_mode_full_init = true; + pr_info("Full heatmap enabled\n"); + } +#endif + + if (panel && panel->funcs && panel->funcs->get_timings && + panel->funcs->get_timings(panel, 1, &timing) > 0) { + coords[0] = timing.hactive.max - 1; + coords[1] = timing.vactive.max - 1; + } else if (of_property_read_u32_array(np, "st,max-coords", coords, 2)) { + pr_err("st,max-coords not found, using 1440x2560\n"); + coords[0] = 1440 - 1; + coords[1] = 2560 - 1; + } + bdata->x_axis_max = coords[0]; + bdata->y_axis_max = coords[1]; + + return OK; +} + +/** + * Probe function, called when the driver it is matched with a device + * with the same name compatible name + * This function allocate, initialize all the most important functions and flow + * those are used by the driver to operate with the IC. + * It allocates device variables, initialize queues and schedule works, + * registers the IRQ handler, suspend/resume callbacks, registers the device + * to the linux input subsystem etc. + */ +#ifdef I2C_INTERFACE +static int fts_probe(struct i2c_client *client, const struct i2c_device_id *idp) +{ +#else +static int fts_probe(struct spi_device *client) +{ +#endif + + struct fts_ts_info *info = NULL; + int error = 0; + struct device_node *dp = client->dev.of_node; + int retval; + int skip_5_1 = 0; + u16 bus_type; + + pr_info("%s: driver probe begin!\n", __func__); + pr_info("driver ver. %s\n", FTS_TS_DRV_VERSION); + + pr_info("SET Bus Functionality :\n"); +#ifdef I2C_INTERFACE + pr_info("I2C interface...\n"); + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + pr_err("Unsupported I2C functionality\n"); + error = -EIO; + goto ProbeErrorExit_0; + } + + pr_info("i2c address: %x\n", client->addr); + bus_type = BUS_I2C; +#else + pr_info("SPI interface...\n"); + + client->bits_per_word = 8; + if (spi_setup(client) < 0) { + pr_err("Unsupported SPI functionality\n"); + error = -EIO; + goto ProbeErrorExit_0; + } + bus_type = BUS_SPI; +#endif + + + pr_info("SET Device driver INFO:\n"); + + + info = kzalloc(sizeof(struct fts_ts_info), GFP_KERNEL); + if (!info) { + pr_err("Out of memory... Impossible to allocate struct info!\n"); + error = -ENOMEM; + goto ProbeErrorExit_0; + } + + info->client = client; + info->dev = &info->client->dev; + +#ifdef DYNAMIC_REFRESH_RATE + /* Set default display refresh rate */ + info->display_refresh_rate = 60; +#endif + + dev_set_drvdata(info->dev, info); + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_TBN) + info->tbn = tbn_init(info->dev); + if (!info->tbn) { + pr_err("ERROR: failed to init tbn context\n"); + error = -ENODEV; + goto ProbeErrorExit_1; + } +#endif + + if (dp) { + info->board = devm_kzalloc(&client->dev, + sizeof(struct fts_hw_platform_data), + GFP_KERNEL); + if (!info->board) { + pr_err("ERROR:info.board kzalloc failed\n"); + goto ProbeErrorExit_1; + } + error = parse_dt(&client->dev, info->board); + if (error) + goto ProbeErrorExit_1; + } + + pr_info("SET Regulators:\n"); + retval = fts_get_reg(info, true); + if (retval < 0) { + pr_err("ERROR: %s: Failed to get regulators\n", __func__); + goto ProbeErrorExit_1; + } + + retval = fts_enable_reg(info, true); + if (retval < 0) { + pr_err("%s: ERROR Failed to enable regulators\n", __func__); + goto ProbeErrorExit_2; + } + + pr_info("SET GPIOS:\n"); + retval = fts_set_gpio(info); + if (retval < 0) { + pr_err("%s: ERROR Failed to set up GPIO's\n", __func__); + goto ProbeErrorExit_2; + } + info->client->irq = gpio_to_irq(info->board->irq_gpio); + + pr_info("SET Pinctrl:\n"); + retval = fts_pinctrl_get(info, true); + if (!retval) + fts_pinctrl_setup(info, true); + + pr_info("SET Event Handler:\n"); + + info->wakesrc = wakeup_source_register(NULL, "fts_tp"); + if (!info->wakesrc) { + pr_err("%s: failed to register wakeup source\n", __func__); + error = -ENODEV; + goto ProbeErrorExit_3; + + } + info->event_wq = alloc_workqueue("fts-event-queue", WQ_UNBOUND | + WQ_HIGHPRI | WQ_CPU_INTENSIVE, 1); + if (!info->event_wq) { + pr_err("ERROR: Cannot create work thread\n"); + error = -ENOMEM; + goto ProbeErrorExit_4; + } + + INIT_WORK(&info->resume_work, fts_resume_work); + INIT_WORK(&info->suspend_work, fts_suspend_work); + + init_completion(&info->bus_resumed); + complete_all(&info->bus_resumed); + + pr_info("SET Input Device Property:\n"); + info->dev = &info->client->dev; + info->input_dev = input_allocate_device(); + if (!info->input_dev) { + pr_err("ERROR: No such input device defined!\n"); + error = -ENODEV; + goto ProbeErrorExit_5; + } + info->input_dev->dev.parent = &client->dev; + info->input_dev->name = FTS_TS_DRV_NAME; + scnprintf(fts_ts_phys, sizeof(fts_ts_phys), "%s/input0", + info->input_dev->name); + info->input_dev->phys = fts_ts_phys; + info->input_dev->id.bustype = bus_type; + info->input_dev->id.vendor = 0x0001; + info->input_dev->id.product = 0x0002; + info->input_dev->id.version = 0x0100; + + __set_bit(EV_SYN, info->input_dev->evbit); + __set_bit(EV_KEY, info->input_dev->evbit); + __set_bit(EV_ABS, info->input_dev->evbit); + __set_bit(BTN_TOUCH, info->input_dev->keybit); + /* __set_bit(BTN_TOOL_FINGER, info->input_dev->keybit); */ + /* __set_bit(BTN_TOOL_PEN, info->input_dev->keybit); */ + + input_mt_init_slots(info->input_dev, TOUCH_ID_MAX, INPUT_MT_DIRECT); + + /* input_mt_init_slots(info->input_dev, TOUCH_ID_MAX); */ + + input_set_abs_params(info->input_dev, ABS_MT_POSITION_X, X_AXIS_MIN, + info->board->x_axis_max, 0, 0); + input_set_abs_params(info->input_dev, ABS_MT_POSITION_Y, Y_AXIS_MIN, + info->board->y_axis_max, 0, 0); + input_set_abs_params(info->input_dev, ABS_MT_TOUCH_MAJOR, AREA_MIN, + AREA_MAX, 0, 0); + input_set_abs_params(info->input_dev, ABS_MT_TOUCH_MINOR, AREA_MIN, + AREA_MAX, 0, 0); + input_set_abs_params(info->input_dev, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER, + MT_TOOL_FINGER, 0, 0); +#ifndef SKIP_PRESSURE + input_set_abs_params(info->input_dev, ABS_MT_PRESSURE, PRESSURE_MIN, + PRESSURE_MAX, 0, 0); +#endif +#ifndef SKIP_DISTANCE + input_set_abs_params(info->input_dev, ABS_MT_DISTANCE, DISTANCE_MIN, + DISTANCE_MAX, 0, 0); +#endif + +#ifdef GESTURE_MODE + input_set_capability(info->input_dev, EV_KEY, KEY_WAKEUP); + + input_set_capability(info->input_dev, EV_KEY, KEY_M); + input_set_capability(info->input_dev, EV_KEY, KEY_O); + input_set_capability(info->input_dev, EV_KEY, KEY_E); + input_set_capability(info->input_dev, EV_KEY, KEY_W); + input_set_capability(info->input_dev, EV_KEY, KEY_C); + input_set_capability(info->input_dev, EV_KEY, KEY_L); + input_set_capability(info->input_dev, EV_KEY, KEY_F); + input_set_capability(info->input_dev, EV_KEY, KEY_V); + input_set_capability(info->input_dev, EV_KEY, KEY_S); + input_set_capability(info->input_dev, EV_KEY, KEY_Z); + input_set_capability(info->input_dev, EV_KEY, KEY_WWW); + + input_set_capability(info->input_dev, EV_KEY, KEY_LEFT); + input_set_capability(info->input_dev, EV_KEY, KEY_RIGHT); + input_set_capability(info->input_dev, EV_KEY, KEY_UP); + input_set_capability(info->input_dev, EV_KEY, KEY_DOWN); + + input_set_capability(info->input_dev, EV_KEY, KEY_F1); + input_set_capability(info->input_dev, EV_KEY, KEY_F2); + input_set_capability(info->input_dev, EV_KEY, KEY_F3); + input_set_capability(info->input_dev, EV_KEY, KEY_F4); + input_set_capability(info->input_dev, EV_KEY, KEY_F5); + + input_set_capability(info->input_dev, EV_KEY, KEY_LEFTBRACE); + input_set_capability(info->input_dev, EV_KEY, KEY_RIGHTBRACE); +#endif + +#ifdef PHONE_KEY + /* KEY associated to the touch screen buttons */ + input_set_capability(info->input_dev, EV_KEY, KEY_HOMEPAGE); + input_set_capability(info->input_dev, EV_KEY, KEY_BACK); + input_set_capability(info->input_dev, EV_KEY, KEY_MENU); +#endif + + mutex_init(&info->diag_cmd_lock); + + mutex_init(&(info->input_report_mutex)); + mutex_init(&info->bus_mutex); + + /* Assume screen is on throughout probe */ + info->bus_refmask = FTS_BUS_REF_SCREEN_ON; + +#ifdef GESTURE_MODE + mutex_init(&gestureMask_mutex); +#endif + + spin_lock_init(&info->fts_int); + + /* register the multi-touch input device */ + error = input_register_device(info->input_dev); + if (error) { + pr_err("ERROR: No such input device\n"); + error = -ENODEV; + goto ProbeErrorExit_5_1; + } + + skip_5_1 = 1; + /* track slots */ + info->touch_id = 0; + info->palm_touch_mask = 0; + info->grip_touch_mask = 0; +#ifdef STYLUS_MODE + info->stylus_id = 0; +#endif + + + /* init feature switches (by default all the features are disable, + * if one feature want to be enabled from the start, + * set the corresponding value to 1)*/ + info->gesture_enabled = 0; + info->glove_enabled = 0; + info->charger_enabled = 0; + info->cover_enabled = 0; + info->grip_enabled = 0; + + info->resume_bit = 1; +#ifdef CONFIG_DRM + info->notifier = fts_noti_block; +#endif + + /* Set initial heatmap mode based on the device tree configuration. + * Default is partial heatmap mode. + */ +#ifdef TOUCHSCREEN_HEATMAP + if (info->board->heatmap_mode_full_init) + info->heatmap_mode = FTS_HEATMAP_FULL; + else + info->heatmap_mode = FTS_HEATMAP_PARTIAL; +#endif + + /* init motion filter mode */ + info->use_default_mf = false; + + /* + * This *must* be done before request_threaded_irq is called. + * Otherwise, if an interrupt is received before request is added, + * but after the interrupt has been subscribed to, pm_qos_req + * may be accessed before initialization in the interrupt handler. + */ + pm_qos_add_request(&info->pm_qos_req, PM_QOS_CPU_DMA_LATENCY, + PM_QOS_DEFAULT_VALUE); + + pr_info("Init Core Lib:\n"); + initCore(info); + /* init hardware device */ + pr_info("Device Initialization:\n"); + error = fts_init(info); + if (error < OK) { + pr_err("Cannot initialize the device ERROR %08X\n", error); + error = -ENODEV; + goto ProbeErrorExit_6; + } + +#ifdef TOUCHSCREEN_HEATMAP + /* + * Heatmap_probe must be called before irq routine is registered, + * because heatmap_read is called from interrupt context. + * This is done as part of fwu_work. + * At the same time, heatmap_probe must be done after fts_init(..) has + * completed, because getForceLen() and getSenseLen() require + * the chip to be initialized. + */ + info->v4l2.parent_dev = info->dev; + info->v4l2.input_dev = info->input_dev; + info->v4l2.read_frame = read_heatmap_raw; + info->v4l2.width = getForceLen(); + info->v4l2.height = getSenseLen(); + /* 120 Hz operation */ + info->v4l2.timeperframe.numerator = 1; + info->v4l2.timeperframe.denominator = 120; + error = heatmap_probe(&info->v4l2); + if (error < OK) + goto ProbeErrorExit_6; +#endif + +#if defined(FW_UPDATE_ON_PROBE) && defined(FW_H_FILE) + pr_info("FW Update and Sensing Initialization:\n"); + error = fts_fw_update(info); + if (error < OK) { + pr_err("Cannot execute fw upgrade the device ERROR %08X\n", + error); + error = -ENODEV; + goto ProbeErrorExit_7; + } + +#else + pr_info("SET Auto Fw Update:\n"); + info->fwu_workqueue = alloc_workqueue("fts-fwu-queue", + WQ_UNBOUND | WQ_HIGHPRI | + WQ_CPU_INTENSIVE, 1); + if (!info->fwu_workqueue) { + pr_err("ERROR: Cannot create fwu work thread\n"); + goto ProbeErrorExit_7; + } + INIT_DELAYED_WORK(&info->fwu_work, fts_fw_update_auto); +#endif + + pr_info("SET Device File Nodes:\n"); + /* sysfs stuff */ + info->attrs.attrs = fts_attr_group; + error = sysfs_create_group(&client->dev.kobj, &info->attrs); + if (error) { + pr_err("ERROR: Cannot create sysfs structure!\n"); + error = -ENODEV; + goto ProbeErrorExit_7; + } + + error = fts_proc_init(); + if (error < OK) + pr_err("Error: can not create /proc file!\n"); + + if (info->fwu_workqueue) + queue_delayed_work(info->fwu_workqueue, &info->fwu_work, + msecs_to_jiffies(EXP_FN_WORK_DELAY_MS)); + + info->touchsim.wq = alloc_workqueue("fts-heatmap_test-queue", + WQ_UNBOUND | WQ_HIGHPRI | + WQ_CPU_INTENSIVE, 1); + + if (info->touchsim.wq) + INIT_WORK(&(info->touchsim.work), touchsim_work); + else + pr_err("ERROR: Cannot create touch sim. test work queue\n"); + + pr_info("Probe Finished!\n"); + + return OK; + + +ProbeErrorExit_7: + if(info->touchsim.wq) + destroy_workqueue(info->touchsim.wq); + +#ifdef CONFIG_DRM + drm_panel_notifier_unregister(info->board->panel, &info->notifier); +#endif + +#ifdef TOUCHSCREEN_HEATMAP + heatmap_remove(&info->v4l2); +#endif + +ProbeErrorExit_6: + pm_qos_remove_request(&info->pm_qos_req); + input_unregister_device(info->input_dev); + +ProbeErrorExit_5_1: + if (skip_5_1 != 1) + input_free_device(info->input_dev); + +ProbeErrorExit_5: + destroy_workqueue(info->event_wq); + +ProbeErrorExit_4: + /* destroy_workqueue(info->fwu_workqueue); */ + wakeup_source_unregister(info->wakesrc); + +ProbeErrorExit_3: + fts_pinctrl_get(info, false); + + fts_enable_reg(info, false); + +ProbeErrorExit_2: + fts_get_reg(info, false); + +ProbeErrorExit_1: + kfree(info); + +ProbeErrorExit_0: + if (error != -EPROBE_DEFER) + pr_err("Probe Failed!\n"); + + return error; +} + + +/** + * Clear and free all the resources associated to the driver. + * This function is called when the driver need to be removed. + */ +#ifdef I2C_INTERFACE +static int fts_remove(struct i2c_client *client) +{ +#else +static int fts_remove(struct spi_device *client) +{ +#endif + + struct fts_ts_info *info = dev_get_drvdata(&(client->dev)); + + /* Force the bus active throughout removal of the client */ + fts_set_bus_ref(info, FTS_BUS_REF_FORCE_ACTIVE, true); + + pr_info("%s\n", __func__); + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_TBN) + tbn_cleanup(info->tbn); +#endif + + fts_proc_remove(); + + /* sysfs stuff */ + sysfs_remove_group(&client->dev.kobj, &info->attrs); + + /* remove interrupt and event handlers */ + fts_interrupt_uninstall(info); + +#ifdef TOUCHSCREEN_HEATMAP + heatmap_remove(&info->v4l2); +#endif + + pm_qos_remove_request(&info->pm_qos_req); + +#ifdef CONFIG_DRM + drm_panel_notifier_unregister(info->board->panel, &info->notifier); +#endif + + /* unregister the device */ + input_unregister_device(info->input_dev); + + /* input_free_device(info->input_dev ); */ + + /* Remove the work thread */ + destroy_workqueue(info->event_wq); + wakeup_source_unregister(info->wakesrc); + + if(info->touchsim.wq) + destroy_workqueue(info->touchsim.wq); + + if (info->fwu_workqueue) + destroy_workqueue(info->fwu_workqueue); + + fts_pinctrl_get(info, false); + + fts_enable_reg(info, false); + fts_get_reg(info, false); + + /* free gpio */ + if (gpio_is_valid(info->board->irq_gpio)) + gpio_free(info->board->irq_gpio); + if (gpio_is_valid(info->board->switch_gpio)) + gpio_free(info->board->switch_gpio); + if (gpio_is_valid(info->board->reset_gpio)) + gpio_free(info->board->reset_gpio); + if (gpio_is_valid(info->board->disp_rate_gpio)) + gpio_free(info->board->disp_rate_gpio); + + /* free any extinfo */ + kfree(info->extinfo.data); + + /* free all */ + kfree(info); + + return OK; +} + +#ifdef CONFIG_PM +static int fts_pm_suspend(struct device *dev) +{ + struct fts_ts_info *info = dev_get_drvdata(dev); + + if (info->bus_refmask) + pr_warn("%s: bus_refmask 0x%X\n", __func__, info->bus_refmask); + + if (info->resume_bit == 1 || info->sensor_sleep == false) { +#ifdef SUPPORT_PROX_PALM + /* Don't block CPU suspend during phone call*/ + if (info->bus_refmask == FTS_BUS_REF_PHONE_CALL) { + fts_enableInterrupt(false); + enable_irq_wake(info->client->irq); + return 0; + } +#endif + pr_warn("%s: can't suspend because touch bus is in use!\n", + __func__); + return -EBUSY; + } + + return 0; +} + +static int fts_pm_resume(struct device *dev) +{ +#ifdef SUPPORT_PROX_PALM + struct fts_ts_info *info = dev_get_drvdata(dev); + if (info->bus_refmask == FTS_BUS_REF_PHONE_CALL) { + fts_enableInterrupt(true); + disable_irq_wake(info->client->irq); + } +#endif + return 0; +} + +static SIMPLE_DEV_PM_OPS(fts_pm_ops, fts_pm_suspend, fts_pm_resume); +#endif + +/** + * Struct which contains the compatible names that need to match with + * the definition of the device in the device tree node + */ +static struct of_device_id fts_of_match_table[] = { + { + .compatible = "st,fts", + }, + {}, +}; + +#ifdef I2C_INTERFACE +static const struct i2c_device_id fts_device_id[] = { + { FTS_TS_DRV_NAME, 0 }, + {} +}; + +static struct i2c_driver fts_i2c_driver = { + .driver = { + .name = FTS_TS_DRV_NAME, + .of_match_table = fts_of_match_table, +#ifdef CONFIG_PM + .pm = &fts_pm_ops, +#endif + }, + .probe = fts_probe, + .remove = fts_remove, + .id_table = fts_device_id, +}; +#else +static struct spi_driver fts_spi_driver = { + .driver = { + .name = FTS_TS_DRV_NAME, + .of_match_table = fts_of_match_table, + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &fts_pm_ops, +#endif + }, + .probe = fts_probe, + .remove = fts_remove, +}; +#endif + + + + +static int __init fts_driver_init(void) +{ +#ifdef I2C_INTERFACE + return i2c_add_driver(&fts_i2c_driver); +#else + return spi_register_driver(&fts_spi_driver); +#endif +} + +static void __exit fts_driver_exit(void) +{ + pr_info("%s\n", __func__); +#ifdef I2C_INTERFACE + i2c_del_driver(&fts_i2c_driver); +#else + spi_unregister_driver(&fts_spi_driver); +#endif +} + + +MODULE_DESCRIPTION("STMicroelectronics MultiTouch IC Driver"); +MODULE_AUTHOR("STMicroelectronics"); +MODULE_LICENSE("GPL v2"); + +late_initcall(fts_driver_init); +module_exit(fts_driver_exit); @@ -0,0 +1,531 @@ +/* + * fts.h + * + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2017, STMicroelectronics + * Authors: AMG(Analog Mems Group) + * + * marco.cali@st.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THE PRESENT SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, FOR THE SOLE + * PURPOSE TO SUPPORT YOUR APPLICATION DEVELOPMENT. + * AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY DIRECT, + * INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM + * THE + * CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING + * INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. + * + * THIS SOFTWARE IS SPECIFICALLY DESIGNED FOR EXCLUSIVE USE WITH ST PARTS. + */ + +/*! + * \file fts.h + * \brief Contains all the definitions and structs used generally by the driver + */ + +#ifndef _LINUX_FTS_I2C_H_ +#define _LINUX_FTS_I2C_H_ + +#define TOUCHSCREEN_HEATMAP + +#include <linux/device.h> +#ifdef TOUCHSCREEN_HEATMAP +#include <linux/input/heatmap.h> +#endif +#include <linux/pm_qos.h> +#include <drm/drm_panel.h> +#include "fts_lib/ftsSoftware.h" +#include "fts_lib/ftsHardware.h" + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_TBN) +#include <linux/input/touch_bus_negotiator.h> +#endif + +#undef DYNAMIC_REFRESH_RATE + +/****************** CONFIGURATION SECTION ******************/ +/** @defgroup conf_section Driver Configuration Section + * Settings of the driver code in order to suit the HW set up and the + *application behavior + * @{ + */ +/* **** CODE CONFIGURATION **** */ +#define FTS_TS_DRV_NAME "fts" /* driver name */ +#define FTS_TS_DRV_VERSION "5.2.16.14" /* driver version string + * */ +#define FTS_TS_DRV_VER 0x0502100E /* driver version u32 format */ + +/* #define DEBUG */ /* /< define to print more logs in the kernel log + * and better follow the code flow */ +#ifdef pr_fmt +#undef pr_fmt +#define pr_fmt(fmt) "[ FTS ] " fmt +#endif + +#define PINCTRL_STATE_ACTIVE "pmx_ts_active" +#define PINCTRL_STATE_SUSPEND "pmx_ts_suspend" +#define PINCTRL_STATE_RELEASE "pmx_ts_release" + +#define DRIVER_TEST /* /< if defined allow to use and test special functions + * of the driver and fts_lib from command shell + * (useful for enginering/debug operations) */ + +/* If both COMPUTE_INIT_METHOD and PRE_SAVED_METHOD are not defined, + * driver will be automatically configured as GOLDEN_VALUE_METHOD + */ +/*#define COMPUTE_INIT_METHOD Allow to compute init data on phone during + * production + */ +#define SKIP_PRODUCTION_TEST /* Allow to skip Production test */ + +//#ifndef COMPUTE_INIT_METHOD +/*#define PRE_SAVED_METHOD Pre-Saved Method used + * during production + */ +//#endif + +/*#define FW_H_FILE*/ /* include the FW data as header file */ +#ifdef FW_H_FILE +#define FW_SIZE_NAME myArray_size /* FW data array size */ +#define FW_ARRAY_NAME myArray /* FW data array name */ +/*#define FW_UPDATE_ON_PROBE*/ /* No delay updating FW */ +#endif + +#ifndef FW_UPDATE_ON_PROBE +/* Include the Production Limit File as header file, can be commented to use a + * .csv file instead */ +/* #define LIMITS_H_FILE */ +#ifdef LIMITS_H_FILE + #define LIMITS_SIZE_NAME myArray2_size /* /< name of the + * variable + * in the limits header + *file which + * specified the + *dimension of + * the limits data + *array */ + #define LIMITS_ARRAY_NAME myArray2 /* /< name of the + * variable in + * the limits header + *file which + * specified the limits + *data array */ +#endif +#else +/* if execute fw update in the probe the limit file must be a .h */ +#define LIMITS_H_FILE /* /< include the Production Limit File as header file, + * DO NOT COMMENT! */ +#define LIMITS_SIZE_NAME myArray2_size /* /< name of the + * variable + * in the limits header + *file + * which specified the + *dimension + * of the limits data + *array */ +#define LIMITS_ARRAY_NAME myArray2 /* /< name of the + * variable in the + * limits header file + *which specified + * the limits data + *array */ +#endif + +/* #define USE_ONE_FILE_NODE */ /* /< allow to enable/disable all the features + * just using one file node */ + +#ifndef FW_UPDATE_ON_PROBE +#define EXP_FN_WORK_DELAY_MS 1000 /* /< time in ms elapsed after the probe + * to start the work which execute FW + *update + * and the Initialization of the IC */ +#endif + +/* **** END **** */ + + +/* **** FEATURES USED IN THE IC **** */ +/* Enable the support of keys */ +/* #define PHONE_KEY */ + +#define GESTURE_MODE /* /< enable the support of the gestures */ +#ifdef GESTURE_MODE + #define USE_GESTURE_MASK /* /< the gestures to select are + * referred using + * a gesture bitmask instead of their + *gesture IDs */ +#endif + + +#define CHARGER_MODE /* /< enable the support to charger mode feature + * (comment to disable) */ + +#define GLOVE_MODE /* /< enable the support to glove mode feature (comment + * to disable) */ + +#define COVER_MODE /* /< enable the support to cover mode feature (comment + * to disable) */ + +#define STYLUS_MODE /* /< enable the support to stylus mode feature (comment + * to disable) */ + +#define GRIP_MODE /* /< enable the support to grip mode feature (comment + * to disable) */ + + +/* **** END **** */ + + +/* **** PANEL SPECIFICATION **** */ +#define X_AXIS_MIN 0 /* /< min X coordinate of the display */ +#define Y_AXIS_MIN 0 /* /< min Y coordinate of the display */ +#define Y_AXIS_MAX 2959 /* /< Max Y coordinate of the display */ +#define X_AXIS_MAX 1440 /* /< Max X coordinate of the display */ + +#define PRESSURE_MIN 0 /* /< min value of pressure reported */ +#define PRESSURE_MAX 127 /* /< Max value of pressure reported */ + +#define DISTANCE_MIN 0 /* /< min distance between the tool and the + * display */ +#define DISTANCE_MAX 127 /* /< Max distance between the tool and the + * display */ + +#define TOUCH_ID_MAX 10 /* /< Max number of simoultaneous touches + * reported */ + +#define AREA_MIN PRESSURE_MIN /* /< min value of Major/minor axis + * reported */ +#define AREA_MAX PRESSURE_MAX /* /< Man value of Major/minor axis + * reported */ +/* **** END **** */ + +/* #define SKIP_PRESSURE */ + +/**@}*/ +/*********************************************************/ +#ifdef TOUCHSCREEN_HEATMAP +/* **** LOCAL HEATMAP FEATURE *** */ +#define LOCAL_HEATMAP_WIDTH 7 +#define LOCAL_HEATMAP_HEIGHT 7 +#define LOCAL_HEATMAP_MODE 0xC1 + +struct heatmap_report { + uint8_t prefix; /* always should be 0xA0 */ + uint8_t mode; /* mode should be 0xC1 for heatmap */ + + uint16_t counter; /* LE order, should increment on each heatmap read */ + int8_t offset_x; + uint8_t size_x; + int8_t offset_y; + uint8_t size_y; + /* data is in LE order; order should be enforced after data is read */ + strength_t data[LOCAL_HEATMAP_WIDTH * LOCAL_HEATMAP_HEIGHT]; +} __attribute__((packed)); +/* **** END **** */ +#endif +/* + * Configuration mode + * + * bitmask which can assume the value defined as features in ftsSoftware.h or + * the following values + */ + +/** @defgroup mode_section IC Status Mode + * Bitmask which keeps track of the features and working mode enabled in the + * IC. + * The meaning of the the LSB of the bitmask must be interpreted considering + * that the value defined in @link feat_opt Feature Selection Option @endlink + * correspond to the position of the corresponding bit in the mask + * @{ + */ +#define MODE_NOTHING 0x00000000 /* /< nothing enabled (sense off) */ +#define MODE_ACTIVE(_mask, _sett) \ + (_mask |= (SCAN_MODE_ACTIVE << 24) | (_sett << 16)) +/* /< store the status of scan mode active and its setting */ +#define MODE_LOW_POWER(_mask, _sett) \ + (_mask |= (SCAN_MODE_LOW_POWER << 24) | (_sett << 16)) +/* /< store the status of scan mode low power and its setting */ +#define IS_POWER_MODE(_mask, _mode) ((_mask&(_mode<<24)) != 0x00) +/* /< check the current mode of the IC */ + +/** @}*/ + +#define CMD_STR_LEN 32 /* /< max number of parameters that can accept + * the + * MP file node (stm_fts_cmd) */ + +#define TSP_BUF_SIZE PAGE_SIZE /* /< max number of bytes printable on + * the shell in the normal file nodes + **/ + +/* Encapsulate display extinfo + * + * For some panels, it is insufficient to simply detect the panel ID and load + * one corresponding firmware. The display driver exposes extended info read + * from the display, but it is up to the touch driver to parse the data. + */ +struct fts_disp_extinfo { + bool is_read; + u8 size; + u8 *data; +}; + +/** + * Struct which contains information about the HW platform and set up + */ +struct fts_hw_platform_data { + int (*power) (bool on); + int switch_gpio;/* (optional) I2C switch */ + int irq_gpio; /* /< number of the gpio associated to the interrupt pin + * */ + int reset_gpio; /* /< number of the gpio associated to the reset pin */ + int disp_rate_gpio; /* disp_rate gpio: LOW=60Hz, HIGH=90Hz */ + const char *vdd_reg_name; /* /< name of the VDD regulator */ + const char *avdd_reg_name; /* /< name of the AVDD regulator */ + const char *fw_name; + const char *limits_name; + bool sensor_inverted; + int x_axis_max; + int y_axis_max; + bool auto_fw_update; +#ifdef TOUCHSCREEN_HEATMAP + bool heatmap_mode_full_init; +#endif + struct drm_panel *panel; + u32 initial_panel_index; + u32 *force_pi_cfg_ver; +}; + +/* Bits for the bus reference mask */ +enum { + FTS_BUS_REF_SCREEN_ON = 0x01, + FTS_BUS_REF_IRQ = 0x02, + FTS_BUS_REF_FW_UPDATE = 0x04, + FTS_BUS_REF_SYSFS = 0x08, + FTS_BUS_REF_FORCE_ACTIVE = 0x10, +#ifdef SUPPORT_PROX_PALM + FTS_BUS_REF_PHONE_CALL = 0x20, +#endif +}; + +/* Motion filter finite state machine (FSM) states + * FTS_MF_FILTERED - default coordinate filtering + * FTS_MF_UNFILTERED - unfiltered single-touch coordinates + * FTS_MF_FILTERED_LOCKED - filtered coordinates. Locked until touch is lifted. + */ +typedef enum { + FTS_MF_FILTERED = 0, + FTS_MF_UNFILTERED = 1, + FTS_MF_FILTERED_LOCKED = 2 +} motion_filter_state_t; + +/* Heatmap mode selection + * FTS_HEATMAP_OFF - no data read + * FTS_HEATMAP_PARTIAL - read partial frame + * (LOCAL_HEATMAP_WIDTH * LOCAL_HEATMAP_HEIGHT) + * FTS_HEATMAP_FULL - read full mutual sense strength frame + */ +#ifdef TOUCHSCREEN_HEATMAP +enum { + FTS_HEATMAP_OFF = 0, + FTS_HEATMAP_PARTIAL = 1, + FTS_HEATMAP_FULL = 2 +}; +#endif +/* + * Forward declaration + */ +struct fts_ts_info; + +/* + * Dispatch event handler + * Return true if the handler has processed a pointer event + */ +typedef bool (*event_dispatch_handler_t) + (struct fts_ts_info *info, unsigned char *data); + +/** + * Driver touch simulation details + */ +struct fts_touchsim{ + /* touch simulation coordinates */ + int x, y, x_step, y_step; + + /* timer to run the touch simulation code */ + struct hrtimer hr_timer; + + struct work_struct work; + struct workqueue_struct *wq; + + /* True if the touch simulation is currently running */ + bool is_running; +}; + +/** + * FTS capacitive touch screen device information + * - dev Pointer to the structure device \n + * - client client structure \n + * - input_dev Input device structure \n + * - work Work thread \n + * - event_wq Event queue for work thread \n + * - event_dispatch_table Event dispatch table handlers \n + * - attrs SysFS attributes \n + * - mode Device operating mode (bitmask) \n + * - touch_id Bitmask for touch id (mapped to input slots) \n + * - stylus_id Bitmask for tracking the stylus touches (mapped using the + * touchId) \n + * - timer Timer when operating in polling mode \n + * - power Power on/off routine \n + * - board HW info retrieved from device tree \n + * - vdd_reg DVDD power regulator \n + * - avdd_reg AVDD power regulator \n + * - resume_bit Indicate if screen off/on \n + * - fwupdate_stat Store the result of a fw update triggered by the host \n + * - notifier Used for be notified from a suspend/resume event \n + * - sensor_sleep true suspend was called, false resume was called \n + * - wakesrc Wakeup Source struct \n + * - input_report_mutex mutex for handling the pressure of keys \n + * - series_of_switches to store the enabling status of a particular feature + * from the host \n + * - tbn Touch Bus Negotiator context + */ +struct fts_ts_info { + struct device *dev; /* Pointer to the device */ +#ifdef I2C_INTERFACE + struct i2c_client *client; /* I2C client structure */ +#else + struct spi_device *client; /* SPI client structure */ +#endif + struct input_dev *input_dev; /* Input device structure */ + + struct work_struct suspend_work; /* Suspend work thread */ + struct work_struct resume_work; /* Resume work thread */ + struct workqueue_struct *event_wq; /* Used for event handler, */ + /* suspend, resume threads */ + + struct completion bus_resumed; /* resume_work complete */ + + struct pm_qos_request pm_qos_req; +#ifdef TOUCHSCREEN_HEATMAP + struct v4l2_heatmap v4l2; +#endif + struct delayed_work fwu_work; /* Work for fw update */ + struct workqueue_struct *fwu_workqueue; /* Fw update work queue */ + event_dispatch_handler_t *event_dispatch_table; /* Dispatch table */ + + struct attribute_group attrs; /* SysFS attributes */ + + unsigned int mode; /* Device operating mode */ + /* MSB - active or lpm */ + unsigned long touch_id; /* Bitmask for touch id */ + unsigned long palm_touch_mask; /* Bitmask for palm touch */ + unsigned long grip_touch_mask; /* Bitmask for grip touch */ +#ifdef STYLUS_MODE + unsigned long stylus_id; /* Bitmask for the stylus */ +#endif + + ktime_t timestamp; /* time that the event was first received from the + touch IC, acquired during hard interrupt, in CLOCK_MONOTONIC */ + + struct fts_hw_platform_data *board; /* HW info from device tree */ + struct regulator *vdd_reg; /* DVDD power regulator */ + struct regulator *avdd_reg; /* AVDD power regulator */ + + struct pinctrl *ts_pinctrl; /* touch pin control state holder */ + struct pinctrl_state *pinctrl_state_active; /* Active pin state*/ + struct pinctrl_state *pinctrl_state_suspend; /* Suspend pin state*/ + struct pinctrl_state *pinctrl_state_release; /* Release pin state*/ + + spinlock_t fts_int; /* Spinlock to protect interrupt toggling */ + bool irq_enabled; /* Interrupt state */ + + struct mutex bus_mutex; /* Protect access to the bus */ + unsigned int bus_refmask; /* References to the bus */ + + int resume_bit; /* Indicate if screen off/on */ + int fwupdate_stat; /* Result of a fw update */ + int reflash_fw; /* Attempt to reflash fw */ + int autotune_stat; /* Attempt to autotune */ + + struct fts_disp_extinfo extinfo; /* Display extended info */ + +#ifdef CONFIG_DRM + struct notifier_block notifier; /* Notify on suspend/resume */ +#endif +#ifdef DYNAMIC_REFRESH_RATE + int display_refresh_rate; /* Display rate in Hz */ +#endif + bool sensor_sleep; /* True if suspend called */ + struct wakeup_source *wakesrc; /* Wake Lock struct */ + + /* input lock */ + struct mutex input_report_mutex; /* Mutex for pressure report */ + + /* switches for features */ + int gesture_enabled; /* Gesture during suspend */ + int glove_enabled; /* Glove mode */ + int charger_enabled; /* Charger mode */ + int stylus_enabled; /* Stylus mode */ + int cover_enabled; /* Cover mode */ + int grip_enabled; /* Grip mode */ +#ifdef TOUCHSCREEN_HEATMAP + int heatmap_mode; /* heatmap mode*/ +#endif +#ifdef SUPPORT_PROX_PALM + int audio_status; + int prox_palm_status; +#endif + /* Stop changing motion filter and keep fw design */ + bool use_default_mf; + /* Motion filter finite state machine (FSM) state */ + motion_filter_state_t mf_state; + /* Time of initial single-finger touch down. This timestamp is used to + * compute the duration a single finger is touched before it is lifted. + */ + ktime_t mf_downtime; + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_TBN) + struct tbn_context *tbn; +#endif + + /* Allow only one thread to execute diag command code*/ + struct mutex diag_cmd_lock; + /* Allow one process to open procfs node */ + bool diag_node_open; + + /* Touch simulation details */ + struct fts_touchsim touchsim; + + u8 scanning_frequency; + + /* Preallocated i/o read buffer */ + u8 io_read_buf[READ_CHUNK + DUMMY_FIFO]; + /* Preallocated i/o write buffer */ + u8 io_write_buf[WRITE_CHUNK + BITS_64 + DUMMY_FIFO]; + /* Preallocated i/o extra write buffer */ + u8 io_extra_write_buf[WRITE_CHUNK + BITS_64 + DUMMY_FIFO]; + +}; + +/* DSI display function used to read panel extinfo */ +int dsi_panel_read_vendor_extinfo(struct drm_panel *panel, char *buffer, + size_t len); + +int fts_chip_powercycle(struct fts_ts_info *info); +extern int input_register_notifier_client(struct notifier_block *nb); +extern int input_unregister_notifier_client(struct notifier_block *nb); + +/* export declaration of functions in fts_proc.c */ +extern int fts_proc_init(void); +extern int fts_proc_remove(void); + +/* Bus reference tracking */ +int fts_set_bus_ref(struct fts_ts_info *info, u16 ref, bool enable); + +#endif diff --git a/fts_lib/ftsCompensation.c b/fts_lib/ftsCompensation.c new file mode 100644 index 0000000..24fc227 --- /dev/null +++ b/fts_lib/ftsCompensation.c @@ -0,0 +1,1123 @@ +/* + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS functions for getting Initialization Data * + * * + ************************************************************************** + ************************************************************************** + * + */ +/*! + * \file ftsCompensation.c + * \brief Contains all the function to work with Initialization Data + */ + +#include "ftsCompensation.h" +#include "ftsCore.h" +#include "ftsError.h" +#include "ftsFrame.h" +#include "ftsHardware.h" +#include "ftsIO.h" +#include "ftsSoftware.h" +#include "ftsTime.h" +#include "ftsTool.h" + + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <stdarg.h> +#include <linux/serio.h> +#include <linux/time.h> +#include <linux/delay.h> +#include <linux/ctype.h> + + +/** + * Request to the FW to load the specified Initialization Data into HDM + * @param type type of Initialization data to load @link load_opt Load Host + * Data Option @endlink + * @return OK if success or an error code which specify the type of error + */ +int requestHDMDownload(u8 type) +{ + int ret = ERROR_OP_NOT_ALLOW; + int retry = 0; + + pr_info("%s: Requesting HDM download...\n", __func__); + while (retry < RETRY_FW_HDM_DOWNLOAD) { + ret = writeSysCmd(SYS_CMD_LOAD_DATA, &type, 1); + /* send request to load in memory the Compensation Data */ + if (ret < OK) { + pr_err("%s: failed at %d attemp!\n", + __func__, retry + 1); + retry += 1; + } else { + pr_info("%s: Request HDM Download FINISHED!\n", + __func__); + return OK; + } + } + + ret |= ERROR_REQU_HDM_DOWNLOAD; + pr_err("%s: Requesting HDM Download... ERROR %08X\n", + __func__, ret); + + return ret; +} + + +/** + * Read HDM Header and check that the type loaded match + * with the one previously requested + * @param type type of Initialization data requested @link load_opt Load Host + * Data Option @endlink + * @param header pointer to DataHeader variable which will contain the header + * @param address pointer to a variable which will contain the updated address + * to the next data + * @return OK if success or an error code which specify the type of error + */ +int readHDMHeader(u8 type, DataHeader *header, u64 *address) +{ + u64 offset = ADDR_FRAMEBUFFER; + u8 data[HDM_DATA_HEADER]; + int ret; + + ret = fts_writeReadU8UX(FTS_CMD_FRAMEBUFFER_R, BITS_16, offset, data, + HDM_DATA_HEADER, DUMMY_FRAMEBUFFER); + if (ret < OK) { /* i2c function have already a retry mechanism */ + pr_err("%s: error while reading HDM data header ERROR %08X\n", + __func__, ret); + return ret; + } + + pr_info("Read HDM Data Header done!\n"); + + if (data[0] != HEADER_SIGNATURE) { + pr_err("%s: The Header Signature was wrong! %02X != %02X ERROR %08X\n", + __func__, data[0], HEADER_SIGNATURE, + ERROR_WRONG_DATA_SIGN); + return ERROR_WRONG_DATA_SIGN; + } + + + if (data[1] != type) { + pr_err("%s: Wrong type found! %02X!=%02X ERROR %08X\n", + __func__, data[1], type, ERROR_DIFF_DATA_TYPE); + return ERROR_DIFF_DATA_TYPE; + } + + pr_info("Type = %02X of Compensation data OK!\n", type); + + header->type = type; + + *address = offset + HDM_DATA_HEADER; + + return OK; +} + + +/** + * Read MS Global Initialization data from the buffer such as Cx1 + * @param address pointer to a variable which contain the address from where + * to read the data and will contain the updated address to the next data + * @param global pointer to MutualSenseData variable which will contain the MS + * initialization data + * @return OK if success or an error code which specify the type of error + */ +static int readMutualSenseGlobalData(u64 *address, MutualSenseData *global) +{ + u8 data[COMP_DATA_GLOBAL]; + int ret; + + pr_info("Address for Global data= %llx\n", *address); + + ret = fts_writeReadU8UX(FTS_CMD_FRAMEBUFFER_R, BITS_16, *address, data, + COMP_DATA_GLOBAL, DUMMY_FRAMEBUFFER); + if (ret < OK) { + pr_err("%s: error while reading info data ERROR %08X\n", + __func__, ret); + return ret; + } + pr_info("Global data Read !\n"); + + global->header.force_node = data[0]; + global->header.sense_node = data[1]; + global->cx1 = data[2]; + /* all other bytes are reserved atm */ + + pr_info("force_len = %d sense_len = %d CX1 = %d\n", + global->header.force_node, global->header.sense_node, + global->cx1); + + *address += COMP_DATA_GLOBAL; + return OK; +} + + +/** + * Read MS Initialization data for each node from the buffer + * @param address a variable which contain the address from where to read the + * data + * @param node pointer to MutualSenseData variable which will contain the MS + * initialization data + * @return OK if success or an error code which specify the type of error + */ +static int readMutualSenseNodeData(u64 address, MutualSenseData *node) +{ + int ret; + int size = node->header.force_node * node->header.sense_node; + + pr_info("Address for Node data = %llx\n", address); + + node->node_data = (i8 *)kmalloc(size * (sizeof(i8)), GFP_KERNEL); + + if (node->node_data == NULL) { + pr_err("%s: can not allocate node_data... ERROR %08X", + __func__, ERROR_ALLOC); + return ERROR_ALLOC; + } + + pr_info("Node Data to read %d bytes\n", size); + ret = fts_writeReadU8UX(FTS_CMD_FRAMEBUFFER_R, BITS_16, address, + node->node_data, size, DUMMY_FRAMEBUFFER); + if (ret < OK) { + pr_err("%s: error while reading node data ERROR %08X\n", + __func__, ret); + kfree(node->node_data); + return ret; + } + node->node_data_size = size; + + pr_info("Read node data OK!\n"); + + return size; +} + +/** + * Perform all the steps to read the necessary info for MS Initialization data + * from the buffer and store it in a MutualSenseData variable + * @param type type of MS Initialization data to read @link load_opt Load Host + * Data Option @endlink + * @param data pointer to MutualSenseData variable which will contain the MS + * initialization data + * @return OK if success or an error code which specify the type of error + */ +int readMutualSenseCompensationData(u8 type, MutualSenseData *data) +{ + int ret; + u64 address; + + data->node_data = NULL; + + if (!(type == LOAD_CX_MS_TOUCH || type == LOAD_CX_MS_LOW_POWER || + type == LOAD_CX_MS_KEY || type == LOAD_CX_MS_FORCE)) { + pr_err("%s: Choose a MS type of compensation data ERROR %08X\n", + __func__, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + ret = requestHDMDownload(type); + if (ret < 0) { + pr_err("%s: ERROR %08X\n", __func__, ret); + return ret; + } + + ret = readHDMHeader(type, &(data->header), &address); + if (ret < 0) { + pr_err("%s: ERROR %08X\n", __func__, ERROR_HDM_DATA_HEADER); + return ret | ERROR_HDM_DATA_HEADER; + } + + ret = readMutualSenseGlobalData(&address, data); + if (ret < 0) { + pr_err("%s: ERROR %08X\n", __func__, ERROR_COMP_DATA_GLOBAL); + return ret | ERROR_COMP_DATA_GLOBAL; + } + + ret = readMutualSenseNodeData(address, data); + if (ret < 0) { + pr_err("%s: ERROR %08X\n", __func__, ERROR_COMP_DATA_NODE); + return ret | ERROR_COMP_DATA_NODE; + } + + return OK; +} + +/** + * Read SS Global Initialization data from the buffer such as Ix1/Cx1 for force + * and sense + * @param address pointer to a variable which contain the address from where + * to read the data and will contain the updated address to the next data + * @param global pointer to MutualSenseData variable which will contain the SS + * initialization data + * @return OK if success or an error code which specify the type of error + */ +static int readSelfSenseGlobalData(u64 *address, SelfSenseData *global) +{ + int ret; + u8 data[COMP_DATA_GLOBAL]; + + pr_info("Address for Global data= %llx\n", *address); + ret = fts_writeReadU8UX(FTS_CMD_FRAMEBUFFER_R, BITS_16, *address, data, + COMP_DATA_GLOBAL, DUMMY_FRAMEBUFFER); + if (ret < OK) { + pr_err("%s: error while reading the data... ERROR %08X\n", + __func__, ret); + return ret; + } + + pr_info("Global data Read !\n"); + + + global->header.force_node = data[0]; + global->header.sense_node = data[1]; + global->f_ix1 = data[2]; + global->s_ix1 = data[3]; + global->f_cx1 = (i8)data[4]; + global->s_cx1 = (i8)data[5]; + global->f_max_n = data[6]; + global->s_max_n = data[7]; + global->f_ix0 = data[8]; + global->s_ix0 = data[9]; + + pr_info("force_len = %d sense_len = %d f_ix1 = %d s_ix1 = %d f_cx1 = %d s_cx1 = %d\n", + global->header.force_node, global->header.sense_node, + global->f_ix1, global->s_ix1, global->f_cx1, global->s_cx1); + pr_info("max_n = %d s_max_n = %d f_ix0 = %d s_ix0 = %d\n", + global->f_max_n, global->s_max_n, global->f_ix0, + global->s_ix0); + + + *address += COMP_DATA_GLOBAL; + + return OK; +} + +/** + * Read SS Initialization data for each node of force and sense channels from + * the buffer + * @param address a variable which contain the address from where to read the + * data + * @param node pointer to SelfSenseData variable which will contain the SS + * initialization data + * @return OK if success or an error code which specify the type of error + */ +static int readSelfSenseNodeData(u64 address, SelfSenseData *node) +{ + int size = node->header.force_node * 2 + node->header.sense_node * 2; + u8 *data; + int ret; + + if (size <= 0) { + pr_err("%s: Invalid SS data length!\n", __func__); + return ERROR_OP_NOT_ALLOW; + } + + data = kzalloc(size * sizeof(u8), GFP_KERNEL); + if (data == NULL) + return ERROR_ALLOC; + + node->ix2_fm = (u8 *)kmalloc(node->header.force_node * (sizeof(u8)), + GFP_KERNEL); + if (node->ix2_fm == NULL) { + pr_err("%s: can not allocate memory for ix2_fm... ERROR %08X", + __func__, ERROR_ALLOC); + kfree(data); + return ERROR_ALLOC; + } + + node->cx2_fm = (i8 *)kmalloc(node->header.force_node * (sizeof(i8)), + GFP_KERNEL); + if (node->cx2_fm == NULL) { + pr_err("%s: can not allocate memory for cx2_fm ... ERROR %08X", + __func__, ERROR_ALLOC); + kfree(node->ix2_fm); + kfree(data); + return ERROR_ALLOC; + } + node->ix2_sn = (u8 *)kmalloc(node->header.sense_node * (sizeof(u8)), + GFP_KERNEL); + if (node->ix2_sn == NULL) { + pr_err("%s: can not allocate memory for ix2_sn ERROR %08X", + __func__, ERROR_ALLOC); + kfree(node->ix2_fm); + kfree(node->cx2_fm); + kfree(data); + return ERROR_ALLOC; + } + node->cx2_sn = (i8 *)kmalloc(node->header.sense_node * (sizeof(i8)), + GFP_KERNEL); + if (node->cx2_sn == NULL) { + pr_err("%s: can not allocate memory for cx2_sn ERROR %08X", + __func__, ERROR_ALLOC); + kfree(node->ix2_fm); + kfree(node->cx2_fm); + kfree(node->ix2_sn); + kfree(data); + return ERROR_ALLOC; + } + + + pr_info("Address for Node data = %llx\n", address); + + pr_info("Node Data to read %d bytes\n", size); + + ret = fts_writeReadU8UX(FTS_CMD_FRAMEBUFFER_R, BITS_16, address, data, + size, DUMMY_FRAMEBUFFER); + if (ret < OK) { + pr_err("%s: error while reading data... ERROR %08X\n", + __func__, ret); + kfree(node->ix2_fm); + kfree(node->cx2_fm); + kfree(node->ix2_sn); + kfree(node->cx2_sn); + kfree(data); + return ret; + } + + pr_info("Read node data ok!\n"); + + memcpy(node->ix2_fm, data, node->header.force_node); + memcpy(node->ix2_sn, &data[node->header.force_node], + node->header.sense_node); + memcpy(node->cx2_fm, &data[node->header.force_node + + node->header.sense_node], + node->header.force_node); + memcpy(node->cx2_sn, &data[node->header.force_node * 2 + + node->header.sense_node], + node->header.sense_node); + + kfree(data); + return OK; +} + +/** + * Perform all the steps to read the necessary info for SS Initialization data + * from the buffer and store it in a SelfSenseData variable + * @param type type of SS Initialization data to read @link load_opt Load Host + * Data Option @endlink + * @param data pointer to SelfSenseData variable which will contain the SS + * initialization data + * @return OK if success or an error code which specify the type of error + */ +int readSelfSenseCompensationData(u8 type, SelfSenseData *data) +{ + int ret; + u64 address; + + data->ix2_fm = NULL; + data->cx2_fm = NULL; + data->ix2_sn = NULL; + data->cx2_sn = NULL; + + if (!(type == LOAD_CX_SS_TOUCH || type == LOAD_CX_SS_TOUCH_IDLE || + type == LOAD_CX_SS_KEY || type == LOAD_CX_SS_FORCE)) { + pr_err("%s: Choose a SS type of compensation data ERROR %08X\n", + __func__, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + ret = requestHDMDownload(type); + if (ret < 0) { + pr_err("%s: error while requesting data... ERROR %08X\n", + __func__, ret); + return ret; + } + + ret = readHDMHeader(type, &(data->header), &address); + if (ret < 0) { + pr_err("%s: error while reading data header... ERROR %08X\n", + __func__, ERROR_HDM_DATA_HEADER); + return ret | ERROR_HDM_DATA_HEADER; + } + + ret = readSelfSenseGlobalData(&address, data); + if (ret < 0) { + pr_err("%s: ERROR %08X\n", __func__, ERROR_COMP_DATA_GLOBAL); + return ret | ERROR_COMP_DATA_GLOBAL; + } + + ret = readSelfSenseNodeData(address, data); + if (ret < 0) { + pr_err("%s: ERROR %08X\n", __func__, ERROR_COMP_DATA_NODE); + return ret | ERROR_COMP_DATA_NODE; + } + + return OK; +} + +/** + * Read TOT MS Global Initialization data from the buffer such as number of + * force and sense channels + * @param address pointer to a variable which contain the address from where + * to read the data and will contain the updated address to the next data + * @param global pointer to a variable which will contain the TOT MS + * initialization data + * @return OK if success or an error code which specify the type of error + */ +static int readTotMutualSenseGlobalData(u64 *address, TotMutualSenseData *global) +{ + int ret; + u8 data[COMP_DATA_GLOBAL]; + + pr_info("Address for Global data= %llx\n", *address); + + ret = fts_writeReadU8UX(FTS_CMD_FRAMEBUFFER_R, BITS_16, *address, data, + COMP_DATA_GLOBAL, DUMMY_FRAMEBUFFER); + if (ret < OK) { + pr_err("%s: error while reading info data ERROR %08X\n", + __func__, ret); + return ret; + } + pr_info("Global data Read !\n"); + + global->header.force_node = data[0]; + global->header.sense_node = data[1]; + /* all other bytes are reserved atm */ + + pr_info("force_len = %d sense_len = %d\n", + global->header.force_node, global->header.sense_node); + + *address += COMP_DATA_GLOBAL; + return OK; +} + + +/** + * Read TOT MS Initialization data for each node from the buffer + * @param address a variable which contain the address from where to read the + * data + * @param node pointer to MutualSenseData variable which will contain the TOT + * MS initialization data + * @return OK if success or an error code which specify the type of error + */ +static int readTotMutualSenseNodeData(u64 address, TotMutualSenseData *node) +{ + int ret, i; + int size = node->header.force_node * node->header.sense_node; + int toRead = size * sizeof(u16); + u8 *data; + + if (size <= 0) { + pr_err("%s: Invalid MS data length!\n", __func__); + return ERROR_OP_NOT_ALLOW; + } + + data = kzalloc(toRead * sizeof(u8), GFP_KERNEL); + if (data == NULL) + return ERROR_ALLOC; + + pr_info("Address for Node data = %llx\n", address); + + node->node_data = (short *)kmalloc(size * (sizeof(short)), GFP_KERNEL); + + if (node->node_data == NULL) { + pr_err("%s: can not allocate node_data... ERROR %08X", + __func__, ERROR_ALLOC); + kfree(data); + return ERROR_ALLOC; + } + + pr_info("Node Data to read %d bytes\n", size); + + ret = fts_writeReadU8UX(FTS_CMD_FRAMEBUFFER_R, BITS_16, address, data, + toRead, DUMMY_FRAMEBUFFER); + if (ret < OK) { + pr_err("%s: error while reading node data ERROR %08X\n", + __func__, ret); + kfree(node->node_data); + kfree(data); + return ret; + } + node->node_data_size = size; + + for (i = 0; i < size; i++) + node->node_data[i] = ((short)data[i * 2 + 1]) << 8 | + data[i * 2]; + + pr_info("Read node data OK!\n"); + + kfree(data); + return size; +} + +/** + * Perform all the steps to read the necessary info for TOT MS Initialization + * data from the buffer and store it in a TotMutualSenseData variable + * @param type type of TOT MS Initialization data to read @link load_opt Load + * Host Data Option @endlink + * @param data pointer to a variable which will contain the TOT MS + * initialization data + * @return OK if success or an error code which specify the type of error + */ +int readTotMutualSenseCompensationData(u8 type, TotMutualSenseData *data) +{ + int ret; + u64 address; + + data->node_data = NULL; + + if (!(type == LOAD_PANEL_CX_TOT_MS_TOUCH || type == + LOAD_PANEL_CX_TOT_MS_LOW_POWER || + type == LOAD_PANEL_CX_TOT_MS_KEY || + type == LOAD_PANEL_CX_TOT_MS_FORCE)) { + pr_err("%s: Choose a TOT MS type of compensation data ERROR %08X\n", + __func__, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + ret = requestHDMDownload(type); + if (ret < 0) { + pr_err("%s: ERROR %08X\n", __func__, ret); + return ret; + } + + ret = readHDMHeader(type, &(data->header), &address); + if (ret < 0) { + pr_err("%s: ERROR %08X\n", __func__, ERROR_HDM_DATA_HEADER); + return ret | ERROR_HDM_DATA_HEADER; + } + + ret = readTotMutualSenseGlobalData(&address, data); + if (ret < 0) { + pr_err("%s: ERROR %08X\n", __func__, ERROR_COMP_DATA_GLOBAL); + return ret | ERROR_COMP_DATA_GLOBAL; + } + + ret = readTotMutualSenseNodeData(address, data); + if (ret < 0) { + pr_err("%s: ERROR %08X\n", __func__, ERROR_COMP_DATA_NODE); + return ret | ERROR_COMP_DATA_NODE; + } + + return OK; +} + +/** + * Read TOT SS Global Initialization data from the buffer such as number of + * force and sense channels + * @param address pointer to a variable which contain the address from where + * to read the data and will contain the updated address to the next data + * @param global pointer to a variable which will contain the TOT SS + * initialization data + * @return OK if success or an error code which specify the type of error + */ +static int readTotSelfSenseGlobalData(u64 *address, TotSelfSenseData *global) +{ + int ret; + u8 data[COMP_DATA_GLOBAL]; + + pr_info("Address for Global data= %llx\n", *address); + ret = fts_writeReadU8UX(FTS_CMD_FRAMEBUFFER_R, BITS_16, *address, data, + COMP_DATA_GLOBAL, DUMMY_FRAMEBUFFER); + if (ret < OK) { + pr_err("%s: error while reading the data... ERROR %08X\n", + __func__, ret); + return ret; + } + + pr_info("Global data Read !\n"); + + + global->header.force_node = data[0]; + global->header.sense_node = data[1]; + + + pr_info("force_len = %d sense_len = %d\n", + global->header.force_node, global->header.sense_node); + + + *address += COMP_DATA_GLOBAL; + + return OK; +} + +/** + * Read TOT SS Global Initialization data from the buffer such as number of + * force and sense channels + * @param address pointer to a variable which contain the address from where + * to read the data and will contain the updated address to the next data + * @param node pointer to a variable which will contain the TOT SS + * initialization data + * @return OK if success or an error code which specify the type of error + */ +static int readTotSelfSenseNodeData(u64 address, TotSelfSenseData *node) +{ + int size = node->header.force_node * 2 + node->header.sense_node * 2; + int toRead = size * 2; /* *2 2 bytes each node */ + u8 *data; + int ret, i, j = 0; + + if (size <= 0) { + pr_err("%s: Invalid Tot SS data length!\n", __func__); + return ERROR_OP_NOT_ALLOW; + } + + data = kzalloc(toRead * sizeof(u8), GFP_KERNEL); + if (data == NULL) + return ERROR_ALLOC; + + node->ix_fm = (u16 *)kmalloc(node->header.force_node * (sizeof(u16)), + GFP_KERNEL); + if (node->ix_fm == NULL) { + pr_err("%s: can not allocate memory for ix2_fm... ERROR %08X", + __func__, ERROR_ALLOC); + kfree(data); + return ERROR_ALLOC; + } + + node->cx_fm = (short *)kmalloc(node->header.force_node * + (sizeof(short)), GFP_KERNEL); + if (node->cx_fm == NULL) { + pr_err("%s: can not allocate memory for cx2_fm ... ERROR %08X", + __func__, ERROR_ALLOC); + kfree(node->ix_fm); + kfree(data); + return ERROR_ALLOC; + } + node->ix_sn = (u16 *)kmalloc(node->header.sense_node * (sizeof(u16)), + GFP_KERNEL); + if (node->ix_sn == NULL) { + pr_err("%s: can not allocate memory for ix2_sn ERROR %08X", + __func__, ERROR_ALLOC); + kfree(node->ix_fm); + kfree(node->cx_fm); + kfree(data); + return ERROR_ALLOC; + } + node->cx_sn = (short *)kmalloc(node->header.sense_node * + (sizeof(short)), GFP_KERNEL); + if (node->cx_sn == NULL) { + pr_err("%s: can not allocate memory for cx2_sn ERROR %08X", + __func__, ERROR_ALLOC); + kfree(node->ix_fm); + kfree(node->cx_fm); + kfree(node->ix_sn); + kfree(data); + return ERROR_ALLOC; + } + + + pr_info("Address for Node data = %llx\n", address); + + pr_info("Node Data to read %d bytes\n", size); + + ret = fts_writeReadU8UX(FTS_CMD_FRAMEBUFFER_R, BITS_16, address, data, + toRead, DUMMY_FRAMEBUFFER); + if (ret < OK) { + pr_err("%s: error while reading data... ERROR %08X\n", + __func__, ret); + kfree(node->ix_fm); + kfree(node->cx_fm); + kfree(node->ix_sn); + kfree(node->cx_sn); + kfree(data); + return ret; + } + + pr_info("Read node data ok!\n"); + + j = 0; + for (i = 0; i < node->header.force_node; i++) { + node->ix_fm[i] = ((u16)data[j + 1]) << 8 | data[j]; + j += 2; + } + + for (i = 0; i < node->header.sense_node; i++) { + node->ix_sn[i] = ((u16)data[j + 1]) << 8 | data[j]; + j += 2; + } + + for (i = 0; i < node->header.force_node; i++) { + node->cx_fm[i] = ((short)data[j + 1]) << 8 | data[j]; + j += 2; + } + + for (i = 0; i < node->header.sense_node; i++) { + node->cx_sn[i] = ((short)data[j + 1]) << 8 | data[j]; + j += 2; + } + + if (j != toRead) + pr_err("%s: parsed a wrong number of bytes %d!=%d\n", + __func__, j, toRead); + + kfree(data); + return OK; +} + +/** + * Perform all the steps to read the necessary info for TOT SS Initialization + * data from the buffer and store it in a TotSelfSenseData variable + * @param type type of TOT MS Initialization data to read @link load_opt Load + * Host Data Option @endlink + * @param data pointer to a variable which will contain the TOT MS + * initialization data + * @return OK if success or an error code which specify the type of error + */ +int readTotSelfSenseCompensationData(u8 type, TotSelfSenseData *data) +{ + int ret; + u64 address; + + data->ix_fm = NULL; + data->cx_fm = NULL; + data->ix_sn = NULL; + data->cx_sn = NULL; + + if (!(type == LOAD_PANEL_CX_TOT_SS_TOUCH || type == + LOAD_PANEL_CX_TOT_SS_TOUCH_IDLE || type == + LOAD_PANEL_CX_TOT_SS_KEY || + type == LOAD_PANEL_CX_TOT_SS_FORCE)) { + pr_err("%s: Choose a TOT SS type of compensation data ERROR %08X\n", + __func__, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + ret = requestHDMDownload(type); + if (ret < 0) { + pr_err("%s: error while requesting data... ERROR %08X\n", + __func__, ret); + return ret; + } + + ret = readHDMHeader(type, &(data->header), &address); + if (ret < 0) { + pr_err("%s: error while reading data header... ERROR %08X\n", + __func__, ERROR_HDM_DATA_HEADER); + return ret | ERROR_HDM_DATA_HEADER; + } + + ret = readTotSelfSenseGlobalData(&address, data); + if (ret < 0) { + pr_err("%s: ERROR %08X\n", __func__, ERROR_COMP_DATA_GLOBAL); + return ret | ERROR_COMP_DATA_GLOBAL; + } + + ret = readTotSelfSenseNodeData(address, data); + if (ret < 0) { + pr_err("%s: ERROR %08X\n", __func__, ERROR_COMP_DATA_NODE); + return ret | ERROR_COMP_DATA_NODE; + } + + return OK; +} + + +/** + * Read Initialization Data Header for the Coefficients and check that the type + * loaded match with the one previously requested + * @param type type of Coefficients data requested @link load_opt Load Host + * Data Option @endlink + * @param msHeader pointer to DataHeader variable for the MS Coefficients + * @param ssHeader pointer to DataHeader variable for the SS Coefficients + * @param address pointer to a variable which will contain the updated address + * to the next data + * @return OK if success or an error code which specify the type of error + */ +static int readSensitivityCoeffHeader(u8 type, DataHeader *msHeader, + DataHeader *ssHeader, u64 *address) +{ + u64 offset = ADDR_FRAMEBUFFER; + u8 data[SYNCFRAME_DATA_HEADER]; + int ret; + + ret = fts_writeReadU8UX(FTS_CMD_FRAMEBUFFER_R, BITS_16, offset, data, + SYNCFRAME_DATA_HEADER, DUMMY_FRAMEBUFFER); + if (ret < OK) { /* i2c function have already a retry mechanism */ + pr_err("%s: error while reading data header ERROR %08X\n", + __func__, ret); + return ret; + } + + pr_info("Read Data Header done!\n"); + + if (data[0] != HEADER_SIGNATURE) { + pr_err("%s: The Header Signature was wrong! %02X != %02X ERROR %08X\n", + __func__, data[0], HEADER_SIGNATURE, + ERROR_WRONG_DATA_SIGN); + return ERROR_WRONG_DATA_SIGN; + } + + + if (data[1] != type) { + pr_err("%s: Wrong type found! %02X!=%02X ERROR %08X\n", + __func__, data[1], type, ERROR_DIFF_DATA_TYPE); + return ERROR_DIFF_DATA_TYPE; + } + + pr_info("Type = %02X of Compensation data OK!\n", type); + + msHeader->type = type; + ssHeader->type = type; + + msHeader->force_node = data[5]; + msHeader->sense_node = data[6]; + pr_info("MS Force Len = %d Sense Len = %d\n", + msHeader->force_node, msHeader->sense_node); + + ssHeader->force_node = data[7]; + ssHeader->sense_node = data[8]; + pr_info("SS Force Len = %d Sense Len = %d\n", + ssHeader->force_node, ssHeader->sense_node); + + *address = offset + SYNCFRAME_DATA_HEADER; + + return OK; +} + + +/** + * Read MS and SS Sensitivity Coefficients for from the IC + * @param address a variable which contain the address from where to read the + * data + * @param msCoeff pointer to MutualSenseCoeff variable which will contain the + * MS Coefficient data + * @param ssCoeff pointer to SelfSenseCoeff variable which will contain the SS + * Coefficient data + * @return OK if success or an error code which specify the type of error + */ +static int readSensitivityCoeffNodeData(u64 address, MutualSenseCoeff *msCoeff, + SelfSenseCoeff *ssCoeff) +{ + int size = msCoeff->header.force_node * msCoeff->header.sense_node + + (ssCoeff->header.force_node + ssCoeff->header.sense_node); + u8 *data; + int ret; + + if (size <= 0) { + pr_err("%s:Invalid SS coeff. length!\n", __func__); + return ERROR_OP_NOT_ALLOW; + } + + data = kzalloc(size * sizeof(u8), GFP_KERNEL); + if (data == NULL) + return ERROR_ALLOC; + + msCoeff->node_data_size = msCoeff->header.force_node * + msCoeff->header.sense_node; + + msCoeff->ms_coeff = (u8 *)kmalloc(msCoeff->node_data_size * + (sizeof(u8)), GFP_KERNEL); + + ssCoeff->ss_force_coeff = (u8 *)kmalloc(ssCoeff->header.force_node * + (sizeof(u8)), GFP_KERNEL); + + ssCoeff->ss_sense_coeff = (u8 *)kmalloc(ssCoeff->header.sense_node * + (sizeof(u8)), GFP_KERNEL); + if (msCoeff->ms_coeff == NULL || + ssCoeff->ss_force_coeff == NULL || + ssCoeff->ss_sense_coeff == NULL) { + + pr_err("%s: can not allocate memory for coeff ERROR %08X", + __func__, ERROR_ALLOC); + + kfree(msCoeff->ms_coeff); + msCoeff->ms_coeff = NULL; + + kfree(ssCoeff->ss_force_coeff); + ssCoeff->ss_force_coeff = NULL; + + kfree(ssCoeff->ss_sense_coeff); + ssCoeff->ss_sense_coeff = NULL; + + kfree(data); + return ERROR_ALLOC; + } + + pr_info("Address for Node data = %llx\n", address); + + pr_info("Node Data to read %d bytes\n", size); + + ret = fts_writeReadU8UX(FTS_CMD_FRAMEBUFFER_R, BITS_16, address, data, + size, DUMMY_FRAMEBUFFER); + if (ret < OK) { + pr_err("%s: error while reading data... ERROR %08X\n", + __func__, ret); + kfree(msCoeff->ms_coeff); + msCoeff->ms_coeff = NULL; + kfree(ssCoeff->ss_force_coeff); + ssCoeff->ss_force_coeff = NULL; + kfree(ssCoeff->ss_sense_coeff); + ssCoeff->ss_sense_coeff = NULL; + kfree(data); + return ret; + } + + pr_info("Read node data ok!\n"); + + memcpy(msCoeff->ms_coeff, data, msCoeff->node_data_size); + memcpy(ssCoeff->ss_force_coeff, &data[msCoeff->node_data_size], + ssCoeff->header.force_node); + memcpy(ssCoeff->ss_sense_coeff, &data[msCoeff->node_data_size + + ssCoeff->header.force_node], + ssCoeff->header.sense_node); + + kfree(data); + return OK; +} + + +/** + * Perform all the steps to read Sensitivity Coefficients and store into the + * corresponding variables + * @param msCoeff pointer to a variable which will contain the MS Sensitivity + * Coefficients + * @param ssCoeff pointer to a variable which will contain the SS Sensitivity + * Coefficients + * @return OK if success or an error code which specify the type of error + */ +int readSensitivityCoefficientsData(MutualSenseCoeff *msCoeff, + SelfSenseCoeff *ssCoeff) +{ + int ret; + u64 address; + + msCoeff->ms_coeff = NULL; + ssCoeff->ss_force_coeff = NULL; + ssCoeff->ss_sense_coeff = NULL; + + + ret = requestHDMDownload(LOAD_SENS_CAL_COEFF); + if (ret < OK) { + pr_err("%s: error while requesting data... ERROR %08X\n", + __func__, ret); + return ret; + } + + ret = readSensitivityCoeffHeader(LOAD_SENS_CAL_COEFF, + &(msCoeff->header), &(ssCoeff->header), + &address); + if (ret < OK) { + pr_err("%s: error while reading data header... ERROR %08X\n", + __func__, ERROR_HDM_DATA_HEADER); + return ret | ERROR_HDM_DATA_HEADER; + } + + ret = readSensitivityCoeffNodeData(address, msCoeff, ssCoeff); + if (ret < OK) { + pr_err("%s: ERROR %08X\n", __func__, ERROR_COMP_DATA_NODE); + return ret | ERROR_COMP_DATA_NODE; + } + + return OK; +} + +/** + * Read Golden Mutual Raw data from FW Host Data Memory. + * @param address a variable which contain the address from where to read the + * data + * @param node pointer to GoldenMutualRawData variable. + * @return OK if success or an error code which specify the type of error + */ +static int readGoldenMutualData(GoldenMutualRawData *pgmData, u64 address) +{ + u32 size, i; + int ret; + + pgmData->data_size = 0; + pgmData->data = NULL; + + pr_info("Address for Golden Mutual hdr = %llx\n", address); + + /* read 12 byte Golden Mutual header */ + ret = fts_writeReadU8UX(FTS_CMD_FRAMEBUFFER_R, BITS_16, + address, (u8 *)&(pgmData->hdr), + GM_DATA_HEADER, DUMMY_FRAMEBUFFER); + if (ret < OK) { + pr_err("error while reading Golden Mutual hdr... ERROR %08X\n", + ret); + goto out; + } + + pr_info("ms_force_len = %u ms_sense_len = %u\n", + pgmData->hdr.ms_f_len, pgmData->hdr.ms_s_len); + pr_info("ss_force_len = %u ss_sense_len = %u\n", + pgmData->hdr.ss_f_len, pgmData->hdr.ss_s_len); + pr_info("ms_key_len = %u \n", pgmData->hdr.ms_k_len); + + size = pgmData->hdr.ms_f_len * pgmData->hdr.ms_s_len; + + pgmData->data = kzalloc(size * sizeof(s16), GFP_KERNEL); + if (pgmData->data == NULL) { + ret = ERROR_ALLOC; + pr_err("Unable to allocate memory for GM raw data. ERR %08X", + ret); + goto out; + } + + /* go past both HDM and GM header to read the data */ + address += GM_DATA_HEADER; + pr_info("Address for Golden Mutual data = %llx\n", address); + + //read the data buffer. + ret = fts_writeReadU8UX(FTS_CMD_FRAMEBUFFER_R, BITS_16, address, + (u8 *)pgmData->data, size * sizeof(s16), + DUMMY_FRAMEBUFFER); + if (ret < OK) { + pr_err("error while reading Golden Mutual data... ERROR %08X\n", ret); + kfree(pgmData->data); + pgmData->data = NULL; + goto out; + } + + pgmData->data_size = size; + + pr_info("Read data ok!\n"); + + for (i = 0; i < size; i++) + le16_to_cpus(&pgmData->data[i]); + + ret = OK; +out: + return ret; +} + +/** + * Perform all the steps to read the necessary info for Golden Mutual raw + * data from the buffer and store it in a GoldemMutualRawData object. + * @param pointer to GoldemMutualRawData variable which will contain + * the raw data. + * @return OK if success or an error code which specify the type of error + */ +int readGoldenMutualRawData(GoldenMutualRawData *pgmData) +{ + int ret; + u64 address; + + ret = requestHDMDownload(LOAD_GOLDEN_MUTUAL_RAW); + if (ret < 0) { + pr_err("error while requesting HDM Download... ERROR %08X\n", + ret); + goto out; + } + + ret = readHDMHeader(LOAD_GOLDEN_MUTUAL_RAW, + &pgmData->hdm_hdr, &address); + if (ret < 0) { + pr_err("error reading HDM header... ERROR %08X\n", + ERROR_HDM_DATA_HEADER); + ret |= ERROR_HDM_DATA_HEADER; + goto out; + } + + ret = readGoldenMutualData(pgmData, address); + if (ret < 0) { + pr_err("error reading Golden Mutual data... ERROR %08X\n", + ERROR_GOLDEN_MUTUAL_DATA); + ret |= ERROR_GOLDEN_MUTUAL_DATA; + goto out; + } + + ret = OK; +out: + return ret; +} diff --git a/fts_lib/ftsCompensation.h b/fts_lib/ftsCompensation.h new file mode 100644 index 0000000..64c07e0 --- /dev/null +++ b/fts_lib/ftsCompensation.h @@ -0,0 +1,181 @@ +/* + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS functions for getting Initialization Data ** + * * + ************************************************************************** + ************************************************************************** + * + */ + +/*! + * \file ftsCompensation.h + * \brief Contains all the definitions and structs to work with Initialization + * Data + */ + +#ifndef FTS_COMPENSATION_H +#define FTS_COMPENSATION_H + +#include "ftsCore.h" +#include "ftsSoftware.h" + + + +#define RETRY_FW_HDM_DOWNLOAD 2 /* /< max number of attempts to + * request HDM download */ + + +/* Bytes dimension of HDM content */ + +#define HDM_DATA_HEADER DATA_HEADER /* /< size in bytes of + * initialization data header */ +#define COMP_DATA_GLOBAL (16 - HDM_DATA_HEADER) /* /< size in bytes + * of initialization + * data general info */ +#define GM_DATA_HEADER (16 - HDM_DATA_HEADER) /* /< size in bytes of + * Golden Mutual data header */ + + +#define HEADER_SIGNATURE 0xA5 /* /< signature used as starting byte of + * data loaded in memory */ + + + +/** + * Struct which contains the general info about Frames and Initialization Data + */ +typedef struct { + int force_node; /* /< Number of Force Channels in the + * frame/Initialization data */ + int sense_node; /* /< Number of Sense Channels in the + * frame/Initialization data */ + int type; /* /< Type of frame/Initialization data */ +} DataHeader; + +/** + * Struct which contains the MS Initialization data + */ +typedef struct { + DataHeader header; /* /< Header */ + i8 cx1; /* /< Cx1 value (can be negative)) */ + i8 *node_data; /* /< Pointer to an array of bytes which contains the + * CX2 data (can be negative) */ + int node_data_size; /* /< size of the data */ +} MutualSenseData; + + +/** + * Struct which contains the SS Initialization data + */ +typedef struct { + DataHeader header; /* /< Header */ + u8 f_ix1; /* /< IX1 Force */ + u8 s_ix1; /* /< IX1 Sense */ + i8 f_cx1; /* /< CX1 Force (can be negative) */ + i8 s_cx1; /* /< CX1 Sense (can be negative) */ + u8 f_max_n; /* /< Force MaxN */ + u8 s_max_n; /* /< Sense MaxN */ + u8 f_ix0; /* /< IX0 Force */ + u8 s_ix0; /* /< IX0 Sense */ + + u8 *ix2_fm; /* /< pointer to an array of bytes which contains Force + * Ix2 data node */ + u8 *ix2_sn; /* /< pointer to an array of bytes which contains Sense + * Ix2 data node */ + i8 *cx2_fm; /* /< pointer to an array of bytes which contains Force + * Cx2 data node + * (can be negative) */ + i8 *cx2_sn; /* /< pointer to an array of bytes which contains Sense + * Cx2 data node + * (can be negative)) */ +} SelfSenseData; + +/** + * Struct which contains the TOT MS Initialization data + */ +typedef struct { + DataHeader header; /* /< Header */ + short *node_data; /* /< pointer to an array of ushort which + * contains TOT MS Initialization data */ + int node_data_size; /* /< size of data */ +} TotMutualSenseData; + +/** + * Struct which contains the TOT SS Initialization data + */ +typedef struct { + DataHeader header; /* /< Header */ + + u16 *ix_fm; /* /< pointer to an array of ushort which contains TOT + * SS IX Force data */ + u16 *ix_sn; /* /< pointer to an array of ushort which contains TOT + * SS IX Sense data */ + short *cx_fm; /* /< pointer to an array of ushort which contains TOT + * SS CX Force data + * (can be negative) */ + short *cx_sn; /* /< pointer to an array of ushort which contains TOT + * SS CX Sense data + * (can be negative) */ +} TotSelfSenseData; + +/** + * Struct which contains the Mutual Sense Sensitivity Calibration Coefficients + */ +typedef struct { + DataHeader header; /* /< Header */ + u8 *ms_coeff; /* /< Pointer to an array of bytes which contains the MS + * Sens coeff */ + int node_data_size; /* /< size of coefficients */ +} MutualSenseCoeff; + +/** + * Struct which contains the Self Sense Sensitivity Calibration Coefficients + */ +typedef struct { + DataHeader header; /* /< Header */ + u8 *ss_force_coeff; /* /< Pointer to an array of bytes which + * contains the SS Sens Force coeff */ + u8 *ss_sense_coeff; /* /< Pointer to an array of bytes which + * contains the SS Sens Sense coeff */ +} SelfSenseCoeff; + +/** + * Struct which contains the Golden Mutual Header + */ +typedef struct { + u8 ms_f_len; /* /< ms force length */ + u8 ms_s_len; /* /< ms sense length */ + u8 ss_f_len; /* /< ss force length */ + u8 ss_s_len; /* /< ss sense length */ + u8 ms_k_len; /* /< ms key length */ + u8 reserved_0[3]; + u32 reserved_1; +}GoldenMutualHdr; + +/** + * Struct which contains the Golden Mutual Raw data + */ +typedef struct { + DataHeader hdm_hdr; /* /< HDM Header */ + GoldenMutualHdr hdr; /* /< Golden Mutual Data Hdr */ + s16 *data; /* /< pointer to the raw data */ + u32 data_size; /* /< size of raw data buffer */ +} GoldenMutualRawData; + +int requestHDMDownload(u8 type); +int readHDMHeader(u8 type, DataHeader *header, u64 *address); +int readMutualSenseCompensationData(u8 type, MutualSenseData *data); +int readSelfSenseCompensationData(u8 type, SelfSenseData *data); +int readTotMutualSenseCompensationData(u8 type, TotMutualSenseData *data); +int readTotSelfSenseCompensationData(u8 type, TotSelfSenseData *data); +int readSensitivityCoefficientsData(MutualSenseCoeff *msData, + SelfSenseCoeff *ssData); +int readGoldenMutualRawData(GoldenMutualRawData *pgmData); + +#endif diff --git a/fts_lib/ftsCore.c b/fts_lib/ftsCore.c new file mode 100644 index 0000000..b073806 --- /dev/null +++ b/fts_lib/ftsCore.c @@ -0,0 +1,1243 @@ +/* + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS Core functions * + * * + ************************************************************************** + ************************************************************************** + * + */ + +/*! + * \file ftsCore.c + * \brief Contains the implementation of the Core functions + */ + +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include "ftsCompensation.h" +#include "ftsCore.h" +#include "ftsError.h" +#include "ftsIO.h" +#include "ftsTest.h" +#include "ftsTime.h" +#include "ftsTool.h" +#include "ftsFrame.h" + +/** @addtogroup system_info + * @{ + */ +SysInfo systemInfo; /* Global System Info variable, accessible in all the + * driver */ +/** @}*/ + +static int reset_gpio = GPIO_NOT_DEFINED;/* /< gpio number of the rest pin, + * the value is GPIO_NOT_DEFINED + * if the reset pin is not connected */ +static int system_reseted_up; /* /< flag checked during resume to understand + * if there was a system reset + * and restore the proper state */ +static int system_reseted_down; /* /< flag checked during suspend to understand + * if there was a system reset + * and restore the proper state */ + +/** + * Initialize core variables of the library. + * Must be called during the probe before any other lib function + * @param info pointer to fts_ts_info which contains info about the device and + * its hw setup + * @return OK if success or an error code which specify the type of error + */ +int initCore(struct fts_ts_info *info) +{ + int ret = OK; + + pr_info("%s: Initialization of the Core...\n", __func__); + ret |= openChannel(info->client); + ret |= resetErrorList(); + ret |= initTestToDo(); + setResetGpio(info->board->reset_gpio); + if (ret < OK) + pr_err("%s: Initialization Core ERROR %08X!\n", + __func__, ret); + else + pr_info("%s: Initialization Finished!\n", + __func__); + return ret; +} + +/** + * Set the reset_gpio variable with the actual gpio number of the board link to + * the reset pin + * @param gpio gpio number link to the reset pin of the IC + */ +void setResetGpio(int gpio) +{ + reset_gpio = gpio; + pr_info("setResetGpio: reset_gpio = %d\n", reset_gpio); +} + +/** + * Perform a system reset of the IC. + * If the reset pin is associated to a gpio, the function execute an hw reset + * (toggling of reset pin) otherwise send an hw command to the IC + * @return OK if success or an error code which specify the type of error + */ +int fts_system_reset(void) +{ + u8 readData[FIFO_EVENT_SIZE]; + int event_to_search; + int res = -1; + int i; + u8 data[1] = { SYSTEM_RESET_VALUE }; +#ifdef SUPPORT_PROX_PALM + struct fts_ts_info *info = NULL; + info = dev_get_drvdata(&getClient()->dev); +#endif + + event_to_search = (int)EVT_ID_CONTROLLER_READY; + + pr_info("System resetting...\n"); + for (i = 0; i < RETRY_SYSTEM_RESET && res < 0; i++) { + resetErrorList(); + fts_enableInterrupt(false); + /* disable interrupt before resetting to be able to get boot + * events */ + + if (reset_gpio == GPIO_NOT_DEFINED) + res = fts_writeU8UX(FTS_CMD_HW_REG_W, ADDR_SIZE_HW_REG, + ADDR_SYSTEM_RESET, data, ARRAY_SIZE( + data)); + else { + gpio_set_value(reset_gpio, 0); + msleep(10); + gpio_set_value(reset_gpio, 1); + res = OK; + } + if (res < OK) + pr_err("fts_system_reset: ERROR %08X\n", ERROR_BUS_W); + else { + res = pollForEvent(&event_to_search, 1, readData, + GENERAL_TIMEOUT); + if (res < OK) + pr_err("fts_system_reset: ERROR %08X\n", res); + } + } +#ifdef SUPPORT_PROX_PALM + info->prox_palm_status = 0; +#endif + if (res < OK) { + pr_err("fts_system_reset...failed after 3 attempts: ERROR %08X\n", + (res | ERROR_SYSTEM_RESET_FAIL)); + return res | ERROR_SYSTEM_RESET_FAIL; + } else { + pr_debug("System reset DONE!\n"); + system_reseted_down = 1; + system_reseted_up = 1; + return OK; + } +} + +/** + * Return the value of system_resetted_down. + * @return the flag value: 0 if not set, 1 if set + */ +int isSystemResettedDown(void) +{ + return system_reseted_down; +} + +/** + * Return the value of system_resetted_up. + * @return the flag value: 0 if not set, 1 if set + */ +int isSystemResettedUp(void) +{ + return system_reseted_up; +} + + +/** + * Set the value of system_reseted_down flag + * @param val value to write in the flag + */ +void setSystemResetedDown(int val) +{ + system_reseted_down = val; +} + +/** + * Set the value of system_reseted_up flag + * @param val value to write in the flag + */ +void setSystemResetedUp(int val) +{ + system_reseted_up = val; +} + + +/** @addtogroup events_group + * @{ + */ + +/** + * Poll the FIFO looking for a specified event within a timeout. Support a + * retry mechanism. + * @param event_to_search pointer to an array of int where each element + * correspond to a byte of the event to find. + * If the element of the array has value -1, the byte of the event, + * in the same position of the element is ignored. + * @param event_bytes size of event_to_search + * @param readData pointer to an array of byte which will contain the event + * found + * @param time_to_wait time to wait before going in timeout + * @return OK if success or an error code which specify the type of error + */ +int pollForEvent(int *event_to_search, int event_bytes, u8 *readData, int + time_to_wait) +{ + const u8 NO_RESPONSE = 0xFF; + const int POLL_SLEEP_TIME_MS = 5; + int i, find, retry, count_err; + int time_to_count; + int err_handling = OK; + StopWatch clock; + + u8 cmd[1] = { FIFO_CMD_READONE }; + char temp[128] = { 0 }; + + find = 0; + retry = 0; + count_err = 0; + time_to_count = time_to_wait / POLL_SLEEP_TIME_MS; + + startStopWatch(&clock); + while (find != 1 && retry < time_to_count && + fts_writeReadU8UX(cmd[0], 0, 0, readData, FIFO_EVENT_SIZE, + DUMMY_FIFO) + >= OK) { + if (readData[0] == NO_RESPONSE || + readData[0] == EVT_ID_NOEVENT) { + /* No events available, so sleep briefly */ + msleep(POLL_SLEEP_TIME_MS); + retry++; + continue; + } else if (readData[0] == EVT_ID_ERROR) { + /* Log of errors */ + pr_err("%s\n", + printHex("ERROR EVENT = ", + readData, + FIFO_EVENT_SIZE, + temp, + sizeof(temp))); + memset(temp, 0, 128); + count_err++; + err_handling = errorHandler(readData, FIFO_EVENT_SIZE); + if ((err_handling & 0xF0FF0000) == + ERROR_HANDLER_STOP_PROC) { + pr_err("pollForEvent: forced to be stopped! ERROR %08X\n", + err_handling); + return err_handling; + } + } else { + pr_info("%s\n", + printHex("READ EVENT = ", readData, + FIFO_EVENT_SIZE, + temp, + sizeof(temp))); + memset(temp, 0, 128); + + if (readData[0] == EVT_ID_CONTROLLER_READY && + event_to_search[0] != EVT_ID_CONTROLLER_READY) { + pr_err("pollForEvent: Unmanned Controller Ready Event! Setting reset flags...\n"); + setSystemResetedUp(1); + setSystemResetedDown(1); + } + } + + find = 1; + + for (i = 0; i < event_bytes; i++) { + if (event_to_search[i] != -1 && (int)readData[i] != + event_to_search[i]) { + find = 0; + break; + } + } + } + stopStopWatch(&clock); + if ((retry >= time_to_count) && find != 1) { + pr_err("pollForEvent: ERROR %08X\n", ERROR_TIMEOUT); + return ERROR_TIMEOUT; + } else if (find == 1) { + pr_info("%s\n", + printHex("FOUND EVENT = ", + readData, + FIFO_EVENT_SIZE, + temp, + sizeof(temp))); + memset(temp, 0, 128); + pr_debug("Event found in %d ms (%d iterations)! Number of errors found = %d\n", + elapsedMillisecond(&clock), retry, count_err); + return count_err; + } else { + pr_err("pollForEvent: ERROR %08X\n", ERROR_BUS_R); + return ERROR_BUS_R; + } +} + +/** @}*/ + +/** + * Check that the FW sent the echo even after a command was sent + * @param cmd pointer to an array of byte which contain the command previously + * sent + * @param size size of cmd + * @return OK if success or an error code which specify the type of error + */ +int checkEcho(u8 *cmd, int size) +{ + int ret, i; + int event_to_search[FIFO_EVENT_SIZE]; + u8 readData[FIFO_EVENT_SIZE]; + + + if (size < 1) { + pr_err("checkEcho: Error Size = %d not valid!\n", size); + return ERROR_OP_NOT_ALLOW; + } else { + if ((size + 4) > FIFO_EVENT_SIZE) + size = FIFO_EVENT_SIZE - 4; + /* Echo event 0x43 0x01 xx xx xx xx xx fifo_status + * therefore command with more than 4 bytes will be trunked */ + + event_to_search[0] = EVT_ID_STATUS_UPDATE; + event_to_search[1] = EVT_TYPE_STATUS_ECHO; + for (i = 2; i < size + 2; i++) + event_to_search[i] = cmd[i - 2]; + if ((cmd[0] == FTS_CMD_SYSTEM) && + (cmd[1] == SYS_CMD_SPECIAL) && + ((cmd[2] == SPECIAL_FULL_PANEL_INIT) || + (cmd[2] == SPECIAL_PANEL_INIT))) + ret = pollForEvent(event_to_search, size + 2, readData, + TIMEOUT_ECHO_FPI); + else if ((cmd[0] == FTS_CMD_SYSTEM) && + (cmd[1] == SYS_CMD_CX_TUNING)) + ret = pollForEvent(event_to_search, size + 2, readData, + TIMEOUT_ECHO_SINGLE_ENDED_SPECIAL_AUTOTUNE); + else if (cmd[0] == FTS_CMD_SYSTEM && + cmd[1] == SYS_CMD_SPECIAL && + cmd[2] == SPECIAL_FIFO_FLUSH) + ret = pollForEvent(event_to_search, size + 2, readData, + TIMEOUT_ECHO_FLUSH); + else + ret = pollForEvent(event_to_search, size + 2, readData, + TIEMOUT_ECHO); + if (ret < OK) { + pr_err("checkEcho: Echo Event not found! ERROR %08X\n", + ret); + return ret | ERROR_CHECK_ECHO_FAIL; + } else if (ret > OK) { + pr_err("checkEcho: Echo Event found but with some error events before! num_error = %d\n", + ret); + return ERROR_CHECK_ECHO_FAIL; + } + + pr_info("ECHO OK!\n"); + return ret; + } +} + + +/** @addtogroup scan_mode + * @{ + */ +/** + * Set a scan mode in the IC + * @param mode scan mode to set; possible values @link scan_opt Scan Mode + * Option @endlink + * @param settings option for the selected scan mode + * (for example @link active_bitmask Active Mode Bitmask @endlink) + * @return OK if success or an error code which specify the type of error + */ +int setScanMode(u8 mode, u8 settings) +{ + u8 cmd[3] = { FTS_CMD_SCAN_MODE, mode, settings }; + u8 cmd1[7] = {0xFA, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00}; + int ret, size = 3; + + pr_debug("%s: Setting scan mode: mode = %02X settings = %02X !\n", + __func__, mode, settings); + if (mode == SCAN_MODE_LOW_POWER) + size = 2; + ret = fts_write(cmd1, 7); + if(ret >= OK) + ret = fts_write(cmd, size); + /* use write instead of writeFw because can be called while the + * interrupt are enabled */ + if (ret < OK) { + pr_err("%s: write failed...ERROR %08X !\n", + __func__, ret); + return ret | ERROR_SET_SCAN_MODE_FAIL; + } + pr_debug("%s: Setting scan mode OK!\n", __func__); + return OK; +} +/** @}*/ + + +/** @addtogroup feat_sel + * @{ + */ +/** + * Set a feature and its option in the IC + * @param feat feature to set; possible values @link feat_opt Feature Selection + * Option @endlink + * @param settings pointer to an array of byte which store the options for + * the selected feature (for example the gesture mask to activate + * @link gesture_opt Gesture IDs @endlink) + * @param size in bytes of settings + * @return OK if success or an error code which specify the type of error + */ +int setFeatures(u8 feat, u8 *settings, int size) +{ + u8 *cmd; + int i = 0; + int ret; + char *buff; + int buff_len = ((2 + 1) * size + 1) * sizeof(char); + int index = 0; + u8 cmd1[7] = {0xFA, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00}; + + cmd = kzalloc((2 + size) * sizeof(u8), GFP_KERNEL); + buff = kzalloc(buff_len, GFP_KERNEL); + if ((buff == NULL) || (cmd == NULL)) { + kfree(buff); + kfree(cmd); + return ERROR_ALLOC; + } + + pr_info("%s: Setting feature: feat = %02X !\n", __func__, feat); + cmd[0] = FTS_CMD_FEATURE; + cmd[1] = feat; + for (i = 0; i < size; i++) { + cmd[2 + i] = settings[i]; + index += scnprintf(buff + index, buff_len - index, + "%02X ", settings[i]); + } + pr_info("%s: Settings = %s\n", __func__, buff); + ret = fts_write(cmd1, 7); + if(ret >= OK) + ret = fts_write(cmd, 2 + size); + /* use write instead of writeFw because can be called while the + * interrupts are enabled */ + if (ret < OK) { + pr_err("%s: write failed...ERROR %08X !\n", __func__, ret); + kfree(buff); + kfree(cmd); + return ret | ERROR_SET_FEATURE_FAIL; + } + pr_info("%s: Setting feature OK!\n", __func__); + kfree(cmd); + kfree(buff); + return OK; +} +/** @}*/ + +/** @addtogroup sys_cmd + * @{ + */ +/** + * Write a system command to the IC + * @param sys_cmd System Command to execute; possible values + * @link sys_opt System Command Option @endlink + * @param sett settings option for the selected system command + * (@link sys_special_opt Special Command Option @endlink, @link ito_opt + * ITO Test Option @endlink, @link load_opt Load Host Data Option @endlink) + * @param size in bytes of settings + * @return OK if success or an error code which specify the type of error + */ +int writeSysCmd(u8 sys_cmd, u8 *sett, int size) +{ + u8 *cmd; + int ret; + char *buff; + int buff_len = ((2 + 1) * size + 1) * sizeof(char); + int index = 0; + u8 cmd1[7] = {0xFA, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00}; + + cmd = kzalloc((2 + size) * sizeof(u8), GFP_KERNEL); + buff = kzalloc(buff_len, GFP_KERNEL); + if ((buff == NULL) || (cmd == NULL)) { + kfree(buff); + kfree(cmd); + return ERROR_ALLOC; + } + + cmd[0] = FTS_CMD_SYSTEM; + cmd[1] = sys_cmd; + + for (ret = 0; ret < size; ret++) { + cmd[2 + ret] = sett[ret]; + index += scnprintf(buff + index, buff_len - index, + "%02X ", sett[ret]); + } + pr_info("%s: Command = %02X %02X %s\n", __func__, cmd[0], + cmd[1], buff); + pr_info("%s: Writing Sys command...\n", __func__); + if (sys_cmd != SYS_CMD_LOAD_DATA) { + ret = fts_write(cmd1, 7); + if(ret >= OK) + ret = fts_writeFwCmd(cmd, 2 + size); + } + else { + if (size >= 1) + ret = requestSyncFrame(sett[0]); + else { + pr_err("%s: No setting argument! ERROR %08X\n", + __func__, ERROR_OP_NOT_ALLOW); + kfree(cmd); + kfree(buff); + return ERROR_OP_NOT_ALLOW; + } + } + if (ret < OK) + pr_err("%s: ERROR %08X\n", __func__, ret); + else + pr_info("%s: FINISHED!\n", __func__); + + kfree(cmd); + kfree(buff); + return ret; +} +/** @}*/ + +/** @addtogroup system_info + * @{ + */ +/** + * Initialize the System Info Struct with default values according to the error + * found during the reading + * @param i2cError 1 if there was an I2C error while reading the System Info + * data from memory, other value if another error occurred + * @return OK if success or an error code which specify the type of error + */ +int defaultSysInfo(int i2cError) +{ + int i; + + pr_info("Setting default System Info...\n"); + + if (i2cError == 1) { + systemInfo.u16_fwVer = 0xFFFF; + systemInfo.u16_cfgProjectId = 0xFFFF; + for (i = 0; i < RELEASE_INFO_SIZE; i++) + systemInfo.u8_releaseInfo[i] = 0xFF; + systemInfo.u16_cxVer = 0xFFFF; + } else { + systemInfo.u16_fwVer = 0x0000; + systemInfo.u16_cfgProjectId = 0x0000; + for (i = 0; i < RELEASE_INFO_SIZE; i++) + systemInfo.u8_releaseInfo[i] = 0x00; + systemInfo.u16_cxVer = 0x0000; + } + + systemInfo.u8_scrRxLen = 0; + systemInfo.u8_scrTxLen = 0; + + pr_info("default System Info DONE!\n"); + return OK; +} + +/** + * Read the System Info data from memory. System Info is loaded automatically + * after every system reset. + * @param request if 1, will be asked to the FW to reload the data, otherwise + * attempt to read it directly from memory + * @return OK if success or an error code which specify the type of error + */ +int readSysInfo(int request) +{ + int ret, i, index = 0; + u8 sett = LOAD_SYS_INFO; + u8 data[SYS_INFO_SIZE] = { 0 }; + char temp[256] = { 0 }; + + if (request == 1) { + pr_info("%s: Requesting System Info...\n", __func__); + + ret = writeSysCmd(SYS_CMD_LOAD_DATA, &sett, 1); + if (ret < OK) { + pr_err("%s: error while writing the sys cmd ERROR %08X\n", + __func__, ret); + goto FAIL; + } + } + + pr_info("%s: Reading System Info...\n", __func__); + ret = fts_writeReadU8UX(FTS_CMD_FRAMEBUFFER_R, BITS_16, + ADDR_FRAMEBUFFER, data, SYS_INFO_SIZE, + DUMMY_FRAMEBUFFER); + if (ret < OK) { + pr_err("%s: error while reading the system data ERROR %08X\n", + __func__, ret); + goto FAIL; + } + + pr_info("%s: Parsing System Info...\n", __func__); + + if (data[0] != HEADER_SIGNATURE) { + pr_err("%s: The Header Signature is wrong! sign: %02X != %02X ERROR %08X\n", + __func__, data[0], HEADER_SIGNATURE, + ERROR_WRONG_DATA_SIGN); + ret = ERROR_WRONG_DATA_SIGN; + goto FAIL; + } + + + if (data[1] != LOAD_SYS_INFO) { + pr_err("%s: The Data ID is wrong! ids: %02X != %02X ERROR %08X\n", + __func__, data[3], LOAD_SYS_INFO, + ERROR_DIFF_DATA_TYPE); + ret = ERROR_DIFF_DATA_TYPE; + goto FAIL; + } + + index += 4; + u8ToU16(&data[index], &systemInfo.u16_apiVer_rev); + index += 2; + systemInfo.u8_apiVer_minor = data[index++]; + systemInfo.u8_apiVer_major = data[index++]; + u8ToU16(&data[index], &systemInfo.u16_chip0Ver); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_chip0Id); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_chip1Ver); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_chip1Id); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_fwVer); + index += 2; + pr_info("FW VER = %04X\n", systemInfo.u16_fwVer); + u8ToU16(&data[index], &systemInfo.u16_svnRev); + index += 2; + pr_info("SVN REV = %04X\n", systemInfo.u16_svnRev); + u8ToU16(&data[index], &systemInfo.u16_cfgVer); + index += 2; + pr_info("CONFIG VER = %04X\n", systemInfo.u16_cfgVer); + u8ToU16(&data[index], &systemInfo.u16_cfgProjectId); + index += 2; + pr_info("CONFIG PROJECT ID = %04X\n", systemInfo.u16_cfgProjectId); + u8ToU16(&data[index], &systemInfo.u16_cxVer); + index += 2; + pr_info("CX VER = %04X\n", systemInfo.u16_cxVer); + u8ToU16(&data[index], &systemInfo.u16_cxProjectId); + index += 2; + pr_info("CX PROJECT ID = %04X\n", systemInfo.u16_cxProjectId); + systemInfo.u8_cfgAfeVer = data[index++]; + systemInfo.u8_cxAfeVer = data[index++]; + systemInfo.u8_panelCfgAfeVer = data[index++]; + pr_info("AFE VER: CFG = %02X - CX = %02X - PANEL = %02X\n", + systemInfo.u8_cfgAfeVer, systemInfo.u8_cxAfeVer, + systemInfo.u8_panelCfgAfeVer); + systemInfo.u8_protocol = data[index++]; + pr_info("Protocol = %02X\n", systemInfo.u8_protocol); + /* index+= 1; */ + /* skip reserved area */ + + /* pr_err("Die Info = "); */ + for (i = 0; i < DIE_INFO_SIZE; i++) + systemInfo.u8_dieInfo[i] = data[index++]; + + /* pr_err("\n"); */ + pr_info("%s\n", + printHex("Die Info = ", + systemInfo.u8_dieInfo, + DIE_INFO_SIZE, temp, sizeof(temp))); + memset(temp, 0, 256); + + + /* pr_err("Release Info = "); */ + for (i = 0; i < RELEASE_INFO_SIZE; i++) + systemInfo.u8_releaseInfo[i] = data[index++]; + + /* pr_err("\n"); */ + + pr_info("%s\n", + printHex("Release Info = ", + systemInfo.u8_releaseInfo, + RELEASE_INFO_SIZE, + temp, + sizeof(temp))); + memset(temp, 0, 256); + + u8ToU32(&data[index], &systemInfo.u32_fwCrc); + index += 4; + u8ToU32(&data[index], &systemInfo.u32_cfgCrc); + index += 4; + + index += 4; /* skip reserved area */ + + systemInfo.u8_mpFlag = data[index++]; + pr_info("MP FLAG = %02X\n", systemInfo.u8_mpFlag); + + index += 3 + 4; /* +3 remaining from mp flag address */ + + systemInfo.u8_ssDetScanSet = data[index]; + pr_info("SS Detect Scan Select = %d\n", + systemInfo.u8_ssDetScanSet); + index += 4; + + u8ToU16(&data[index], &systemInfo.u16_scrResX); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_scrResY); + index += 2; + pr_info("Screen Resolution = %d x %d\n", + systemInfo.u16_scrResX, systemInfo.u16_scrResY); + systemInfo.u8_scrTxLen = data[index++]; + pr_info("TX Len = %d\n", systemInfo.u8_scrTxLen); + systemInfo.u8_scrRxLen = data[index++]; + pr_info("RX Len = %d\n", systemInfo.u8_scrRxLen); + systemInfo.u8_keyLen = data[index++]; + pr_info("Key Len = %d\n", systemInfo.u8_keyLen); + systemInfo.u8_forceLen = data[index++]; + pr_info("Force Len = %d\n", systemInfo.u8_forceLen); + index += 8; + + u8ToU32(&data[index], &systemInfo.u32_productionTimestamp); + pr_info("Production Timestamp = %08X\n", + systemInfo.u32_productionTimestamp); + + index += 32; /* skip reserved area */ + + u8ToU16(&data[index], &systemInfo.u16_dbgInfoAddr); + index += 2; + + index += 6; /* skip reserved area */ + + u8ToU16(&data[index], &systemInfo.u16_msTchRawAddr); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_msTchFilterAddr); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_msTchStrenAddr); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_msTchBaselineAddr); + index += 2; + + u8ToU16(&data[index], &systemInfo.u16_ssTchTxRawAddr); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_ssTchTxFilterAddr); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_ssTchTxStrenAddr); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_ssTchTxBaselineAddr); + index += 2; + + u8ToU16(&data[index], &systemInfo.u16_ssTchRxRawAddr); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_ssTchRxFilterAddr); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_ssTchRxStrenAddr); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_ssTchRxBaselineAddr); + index += 2; + + u8ToU16(&data[index], &systemInfo.u16_keyRawAddr); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_keyFilterAddr); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_keyStrenAddr); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_keyBaselineAddr); + index += 2; + + u8ToU16(&data[index], &systemInfo.u16_frcRawAddr); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_frcFilterAddr); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_frcStrenAddr); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_frcBaselineAddr); + index += 2; + + u8ToU16(&data[index], &systemInfo.u16_ssHvrTxRawAddr); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_ssHvrTxFilterAddr); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_ssHvrTxStrenAddr); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_ssHvrTxBaselineAddr); + index += 2; + + u8ToU16(&data[index], &systemInfo.u16_ssHvrRxRawAddr); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_ssHvrRxFilterAddr); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_ssHvrRxStrenAddr); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_ssHvrRxBaselineAddr); + index += 2; + + u8ToU16(&data[index], &systemInfo.u16_ssPrxTxRawAddr); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_ssPrxTxFilterAddr); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_ssPrxTxStrenAddr); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_ssPrxTxBaselineAddr); + index += 2; + + u8ToU16(&data[index], &systemInfo.u16_ssPrxRxRawAddr); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_ssPrxRxFilterAddr); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_ssPrxRxStrenAddr); + index += 2; + u8ToU16(&data[index], &systemInfo.u16_ssPrxRxBaselineAddr); + index += 2; + + u8ToU16(&data[index], &systemInfo.u16_ssDetRawAddr); + index += 2; + + u8ToU16(&data[index], &systemInfo.u16_ssDetFilterAddr); + index += 2; + + u8ToU16(&data[index], &systemInfo.u16_ssDetStrenAddr); + index += 2; + + u8ToU16(&data[index], &systemInfo.u16_ssDetBaselineAddr); + index += 2; + + pr_info("Parsed %d bytes!\n", index); + + + if (index != SYS_INFO_SIZE) { + pr_err("%s: index = %d different from %d ERROR %08X\n", + __func__, index, SYS_INFO_SIZE, + ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + pr_info("System Info Read DONE!\n"); + return OK; + +FAIL: + defaultSysInfo(isI2cError(ret)); + return ret; +} +/** @}*/ + + +/** + * Read data from the Config Memory + * @param offset Starting address in the Config Memory of data to read + * @param outBuf pointer of a byte array which contain the bytes to read + * @param len number of bytes to read + * @return OK if success or an error code which specify the type of error + */ +int readConfig(u16 offset, u8 *outBuf, int len) +{ + int ret; + u64 final_address = offset + ADDR_CONFIG_OFFSET; + + pr_info("%s: Starting to read config memory at %llx ...\n", + __func__, final_address); + ret = fts_writeReadU8UX(FTS_CMD_CONFIG_R, BITS_16, final_address, + outBuf, len, DUMMY_CONFIG); + if (ret < OK) { + pr_err("%s: Impossible to read Config Memory... ERROR %08X!\n", + __func__, ret); + return ret; + } + + pr_info("%s: Read config memory FINISHED!\n", __func__); + return OK; +} + +/** + * Write data into the Config Memory + * @param offset Starting address in the Config Memory where write the data + * @param data pointer of a byte array which contain the data to write + * @param len number of bytes to write + * @return OK if success or an error code which specify the type of error + */ +int writeConfig(u16 offset, u8 *data, int len) +{ + int ret; + u64 final_address = offset + ADDR_CONFIG_OFFSET; + + pr_info("%s: Starting to write config memory at %llx ...\n", + __func__, final_address); + ret = fts_writeU8UX(FTS_CMD_CONFIG_W, BITS_16, final_address, data, + len); + if (ret < OK) { + pr_err("%s: Impossible to write Config Memory... ERROR %08X!\n", + __func__, ret); + return ret; + } + + pr_info("%s: Write config memory FINISHED!\n", __func__); + return OK; +} + +/* Set the interrupt state + * @param enable Indicates whether interrupts should enabled. + * @return OK if success + */ +int fts_enableInterrupt(bool enable) +{ + struct fts_ts_info *info = NULL; + unsigned long flag; + + if (getClient() == NULL) { + pr_err("Cannot get client irq. Error = %08X\n", + ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + info = dev_get_drvdata(&getClient()->dev); + + spin_lock_irqsave(&info->fts_int, flag); + + if (enable == info->irq_enabled) + pr_debug("Interrupt is already set (enable = %d).\n", enable); + else { + info->irq_enabled = enable; + if (enable) { + enable_irq(getClient()->irq); + pr_debug("Interrupt enabled.\n"); + } else { + disable_irq_nosync(getClient()->irq); + pr_debug("Interrupt disabled.\n"); + } + } + + spin_unlock_irqrestore(&info->fts_int, flag); + return OK; +} + +/** + * Check if there is a crc error in the IC which prevent the fw to run. + * @return OK if no CRC error, or a number >OK according the CRC error found + */ +int fts_crc_check(void) +{ + u8 val; + u8 crc_status; + int res; + u8 error_to_search[6] = { EVT_TYPE_ERROR_CRC_CFG_HEAD, + EVT_TYPE_ERROR_CRC_CFG, + EVT_TYPE_ERROR_CRC_CX, + EVT_TYPE_ERROR_CRC_CX_HEAD, + EVT_TYPE_ERROR_CRC_CX_SUB, + EVT_TYPE_ERROR_CRC_CX_SUB_HEAD }; + + + res = fts_writeReadU8UX(FTS_CMD_HW_REG_R, ADDR_SIZE_HW_REG, ADDR_CRC, + &val, 1, DUMMY_HW_REG); + /* read 2 bytes because the first one is a dummy byte! */ + if (res < OK) { + pr_err("%s Cannot read crc status ERROR %08X\n", __func__, res); + return res; + } + + crc_status = val & CRC_MASK; + if (crc_status != OK) { /* CRC error if crc_status!=0 */ + pr_err("%s CRC ERROR = %02X\n", __func__, crc_status); + return CRC_CODE; + } + + pr_info("%s: Verifying if Config CRC Error...\n", __func__); + res = fts_system_reset(); + if (res >= OK) { + res = pollForErrorType(error_to_search, 2); + if (res < OK) { + pr_info("%s: No Config CRC Error Found!\n", __func__); + pr_info("%s: Verifying if Cx CRC Error...\n", __func__); + res = pollForErrorType(&error_to_search[2], 4); + if (res < OK) { + pr_info("%s: No Cx CRC Error Found!\n", + __func__); + return OK; + } else { + pr_err("%s: Cx CRC Error found! CRC ERROR = %02X\n", + __func__, res); + return CRC_CX; + } + } else { + pr_err("%s: Config CRC Error found! CRC ERROR = %02X\n", + __func__, res); + return CRC_CONFIG; + } + } else { + pr_err("%s: Error while executing system reset! ERROR %08X\n", + __func__, res); + return res; + } + + return OK; +} + +/** + * Request a host data and use the sync method to understand when the FW load + * it + * @param type the type ID of host data to load (@link load_opt Load Host Data + * Option @endlink) + * @return OK if success or an error code which specify the type of error + */ +int requestSyncFrame(u8 type) +{ + u8 request[3] = { FTS_CMD_SYSTEM, SYS_CMD_LOAD_DATA, type }; + u8 readData[DATA_HEADER] = { 0 }; + int ret, retry = 0, retry2 = 0, time_to_count; + int count, new_count; + u8 cmd[7] = {0xFA, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00}; + + pr_info("%s: Starting to get a sync frame...\n", __func__); + + while (retry2 < RETRY_MAX_REQU_DATA) { + pr_info("%s: Reading count...\n", __func__); + + ret = fts_writeReadU8UX(FTS_CMD_FRAMEBUFFER_R, BITS_16, + ADDR_FRAMEBUFFER, readData, DATA_HEADER, + DUMMY_FRAMEBUFFER); + if (ret < OK) { + pr_err("%s: Error while reading count! ERROR %08X\n", + __func__, ret | ERROR_REQU_DATA); + ret |= ERROR_REQU_DATA; + retry2++; + continue; + } + + if (readData[0] != HEADER_SIGNATURE) + pr_err("%s: Invalid Signature while reading count! ERROR %08X\n", + __func__, ret | ERROR_REQU_DATA); + + count = (readData[3] << 8) | readData[2]; + new_count = count; + pr_info("%s: Base count = %d\n", __func__, count); + + pr_info("%s: Requesting frame %02X attempt = %d\n", + __func__, type, retry2 + 1); + ret = fts_write(cmd, 7); + if(ret >= OK) + ret = fts_write(request, ARRAY_SIZE(request)); + if (ret >= OK) { + pr_info("%s: Polling for new count...\n", __func__); + time_to_count = TIMEOUT_REQU_DATA / TIMEOUT_RESOLUTION; + while (count == new_count && retry < time_to_count) { + ret = fts_writeReadU8UX(FTS_CMD_FRAMEBUFFER_R, + BITS_16, + ADDR_FRAMEBUFFER, + readData, + DATA_HEADER, + DUMMY_FRAMEBUFFER); + if ((ret >= OK) && (readData[0] == + HEADER_SIGNATURE) && + (readData[1] == type)) + new_count = ((readData[3] << 8) | + readData[2]); + else + pr_err("%s: invalid Signature or can not read count... ERROR %08X\n", + __func__, ret); + retry++; + mdelay(TIMEOUT_RESOLUTION); + } + + if (count == new_count) { + pr_err("%s: New count not received! ERROR %08X\n", + __func__, + ERROR_TIMEOUT | ERROR_REQU_DATA); + ret = ERROR_TIMEOUT | ERROR_REQU_DATA; + } else { + pr_info("%s: New count found! count = %d! Frame ready!\n", + __func__, new_count); + return OK; + } + } + retry2++; + } + pr_err("%s: Request Data failed! ERROR %08X\n", __func__, ret); + return ret; +} + +/** + * Set the Active Scanning Frequency to a defined value + * @param freq scanning frequency to set in Hz + * @return OK if success or an error code which specify the type of error + * @warning The scan frequency can be set only for the MS scan! + */ +int setActiveScanFrequency(u32 freq) +{ + int res; + u8 temp[2] = { 0 }; + u16 t_cycle; + + pr_info("%s: Setting the scanning frequency to %uHz...\n", + __func__, freq); + + /* read MRN register */ + res = readConfig(ADDR_CONFIG_MRN, temp, 1); + if (res < OK) { + pr_err("%s: error while reading mrn count! ERROR %08X\n", + __func__, res); + return res; + } + + /* setting r count to 0 (= 1 R cycle used) and write it back */ + temp[0] &= (~0x03); + res = writeConfig(ADDR_CONFIG_MRN, temp, 1); + if (res < OK) { + pr_err("%s: error while writing mrn count! ERROR %08X\n", + __func__, res); + return res; + } + + /* set first R cycle slot according the specified frequency */ + /* read T cycle */ + res = readConfig(ADDR_CONFIG_T_CYCLE, temp, 2); + if (res < OK) { + pr_err("%s: error while reading T cycle! ERROR %08X\n", + __func__, res); + return res; + } + t_cycle = ((u16)(temp[1] << 8)) | temp[0]; + + + /* compute the value of R cycle according the formula + * scan_freq = 30Mhz/(2*(T_cycle+R_cycle)) */ + temp[0] = (30000000) / (freq * 2) - t_cycle; + /* write R cycle in Config Area */ + pr_info("%s: T cycle = %d (0x%04X) => R0 cycle = %d (0x%02X)\n", + __func__, t_cycle, t_cycle, temp[0], temp[0]); + res = writeConfig(ADDR_CONFIG_R0_CYCLE, temp, 1); + if (res < OK) { + pr_err("%s: error while writing R0 cycle! ERROR %08X\n", + __func__, res); + return res; + } + + pr_info("%s: Saving Config into the flash ...\n", __func__); + /* save config */ + temp[0] = SAVE_FW_CONF; + res = writeSysCmd(SYS_CMD_SAVE_FLASH, temp, 1); + if (res < OK) { + pr_err("%s: error while saving config into the flash! ERROR %08X\n", + __func__, res); + return res; + } + + /* system reset */ + res = fts_system_reset(); + if (res < OK) { + pr_err("%s: error at system reset! ERROR %08X\n", + __func__, res); + return res; + } + + pr_info("%s: Setting the scanning frequency FINISHED!\n", __func__); + return OK; +} + +/** + * Write Host Data Memory + * @param type type of data to write + * @param data pointer to the data which are written + * @param msForceLen number of force (Tx) channels used with Mutual + * @param msSenseLen number of sense (Rx) channels used with Mutual + * @param ssForceLen number of force (Tx) channel used with Self + * @param ssSenseLen number of sense (Rx) channel used with Self + * @param save if =1 will save the host data written into the flash + * @return OK if success or an error code which specify the type of error + */ +int writeHostDataMemory(u8 type, u8 *data, u8 msForceLen, u8 msSenseLen, + u8 ssForceLen, u8 ssSenseLen, int save) +{ + int res; + int size = (msForceLen * msSenseLen) + (ssForceLen + ssSenseLen); + u8 sett = SPECIAL_WRITE_HOST_MEM_TO_FLASH; + u8 *temp; + + temp = kzalloc((size + SYNCFRAME_DATA_HEADER) * sizeof(u8), GFP_KERNEL); + if (temp == NULL) + return ERROR_ALLOC; + + pr_info("%s: Starting to write Host Data Memory\n", __func__); + + temp[0] = 0x5A; + temp[1] = type; + temp[5] = msForceLen; + temp[6] = msSenseLen; + temp[7] = ssForceLen; + temp[8] = ssSenseLen; + + memcpy(&temp[16], data, size); + + pr_info("%s: Write Host Data Memory in buffer...\n", __func__); + res = fts_writeU8UX(FTS_CMD_FRAMEBUFFER_W, BITS_16, + ADDR_FRAMEBUFFER, temp, size + + SYNCFRAME_DATA_HEADER); + + if (res < OK) { + pr_err("%s: error while writing the buffer! ERROR %08X\n", + __func__, res); + kfree(temp); + return res; + } + + /* save host data memory into the flash */ + if (save == 1) { + pr_info("%s: Trigger writing into the flash...\n", __func__); + res = writeSysCmd(SYS_CMD_SPECIAL, &sett, 1); + if (res < OK) { + pr_err("%s: error while writing into the flash! ERROR %08X\n", + __func__, res); + kfree(temp); + return res; + } + } + + + pr_info("%s: write Host Data Memory FINISHED!\n", __func__); + kfree(temp); + return OK; +} + +/* + * Save MP flag value into the flash + * @param mpflag Value to write in the MP Flag field + * @return OK if success or an error code which specify the type of error + */ +int saveMpFlag(u8 mpflag) +{ + int ret = OK; + u8 panelCfg = SAVE_PANEL_CONF; + + pr_info("%s: Saving MP Flag = %02X\n", __func__, mpflag); + ret |= writeSysCmd(SYS_CMD_MP_FLAG, &mpflag, 1); + if (ret < OK) + pr_err("%s: Error while writing MP flag on ram... ERROR %08X\n", + __func__, ret); + + ret |= writeSysCmd(SYS_CMD_SAVE_FLASH, &panelCfg, 1); + if (ret < OK) + pr_err("%s: Error while saving MP flag on flash... ERROR %08X\n", + __func__, ret); + + ret |= readSysInfo(1); + if (ret < OK) { + pr_err("%s: Error while refreshing SysInfo... ERROR %08X\n", + __func__, ret); + return ret; + } + + pr_info("%s: Saving MP Flag OK!\n", __func__); + return OK; +} diff --git a/fts_lib/ftsCore.h b/fts_lib/ftsCore.h new file mode 100644 index 0000000..0a4c9ae --- /dev/null +++ b/fts_lib/ftsCore.h @@ -0,0 +1,216 @@ +/* + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS Core definitions ** + * * + ************************************************************************** + ************************************************************************** + * + */ + +/*! + * \file ftsCore.h + * \brief Contains all the definitions and structs of Core functionalities + */ + +#ifndef FTS_CORE_H +#define FTS_CORE_H + +#include "ftsHardware.h" +#include "ftsSoftware.h" +#include "../fts.h" + +/* HW DATA */ +#define GPIO_NOT_DEFINED -1 /* /< value assumed by reset_gpio when + * the reset pin of the IC is not + * connected */ + + +#define ADDR_SIZE_HW_REG BITS_32 /* /< value of AddrSize for Hw register + * in FTI @see AddrSize */ + +#define DATA_HEADER 4 /* /< size in byte of the header loaded + * with the data in the frambuffer */ + +/** + * Type of CRC errors + */ +typedef enum { + CRC_CODE = 1, /* /< CRC in the code section */ + CRC_CONFIG = 2, /* /< CRC in the config section */ + CRC_CX = 3, /* /< CRC in the cx section */ + CRC_PANEL = 4 /* /< CRC in the panel section */ +} CRC_Error; + +/* CHIP INFO */ +/** @defgroup system_info System Info + * System Info Data collect the most important information about hw and fw + * @{ + */ +/* Size in bytes of System Info data */ +#define SYS_INFO_SIZE 216 /* Num bytes of die info */ +#define DIE_INFO_SIZE 16 /* Num bytes of external release + * in config */ +#define EXTERNAL_RELEASE_INFO_SIZE 8 /* Num bytes of release info in + * sys info + * (first bytes are external + * release) */ +#define RELEASE_INFO_SIZE (EXTERNAL_RELEASE_INFO_SIZE) +/** @}*/ + +/* RETRY MECHANISM */ +#define RETRY_MAX_REQU_DATA 2 /* /< Max number of attempts + * performed + * when requesting data */ +#define RETRY_SYSTEM_RESET 3 /* /< Max number of attempts + * performed + * to reset the IC */ + +/** @addtogroup system_info + * @{ + */ + +/** + * Struct which contains fundamental information about the chip and its + * configuration + */ +typedef struct { + u16 u16_apiVer_rev; /* /< API revision version */ + u8 u8_apiVer_minor; /* /< API minor version */ + u8 u8_apiVer_major; /* /< API major version */ + u16 u16_chip0Ver; /* /< Dev0 version */ + u16 u16_chip0Id; /* /< Dev0 ID */ + u16 u16_chip1Ver; /* /< Dev1 version */ + u16 u16_chip1Id; /* /< Dev1 ID */ + u16 u16_fwVer; /* /< Fw version */ + u16 u16_svnRev; /* /< SVN Revision */ + u16 u16_cfgVer; /* /< Config Version */ + u16 u16_cfgProjectId; /* /< Config Project ID */ + u16 u16_cxVer; /* /< Cx Version */ + u16 u16_cxProjectId; /* /< Cx Project ID */ + u8 u8_cfgAfeVer; /* /< AFE version in Config */ + u8 u8_cxAfeVer; /* /< AFE version in CX */ + u8 u8_panelCfgAfeVer; /* /< AFE version in PanelMem */ + u8 u8_protocol; /* /< Touch Report Protocol */ + u8 u8_dieInfo[DIE_INFO_SIZE]; /* /< Die information */ + u8 u8_releaseInfo[RELEASE_INFO_SIZE]; /* /< Release information */ + u32 u32_fwCrc; /* /< Crc of FW */ + u32 u32_cfgCrc; /* /< Crc of config */ + u8 u8_mpFlag; /* /< MP Flag */ + u8 u8_ssDetScanSet; /* /< Type of Detect Scan Selected */ + + u16 u16_scrResX;/* /< X resolution on main screen */ + u16 u16_scrResY;/* /< Y resolution on main screen */ + u8 u8_scrTxLen; /* /< Tx length */ + u8 u8_scrRxLen; /* /< Rx length */ + u8 u8_keyLen; /* /< Key Len */ + u8 u8_forceLen; /* /< Force Len */ + u32 u32_productionTimestamp; /* /< Production Timestamp */ + + u16 u16_dbgInfoAddr; /* /< Offset of debug Info structure */ + + u16 u16_msTchRawAddr; /* /< Offset of MS touch raw frame */ + u16 u16_msTchFilterAddr;/* /< Offset of MS touch filter frame */ + u16 u16_msTchStrenAddr; /* /< Offset of MS touch strength frame */ + u16 u16_msTchBaselineAddr; /* /< Offset of MS touch baseline frame + * */ + + u16 u16_ssTchTxRawAddr; /* /< Offset of SS touch force raw frame */ + u16 u16_ssTchTxFilterAddr; /* /< Offset of SS touch force filter + * frame */ + u16 u16_ssTchTxStrenAddr;/* /< Offset of SS touch force strength frame + * */ + u16 u16_ssTchTxBaselineAddr; /* /< Offset of SS touch force baseline + * frame */ + + u16 u16_ssTchRxRawAddr; /* /< Offset of SS touch sense raw frame */ + u16 u16_ssTchRxFilterAddr; /* /< Offset of SS touch sense filter + * frame */ + u16 u16_ssTchRxStrenAddr;/* /< Offset of SS touch sense strength frame + * */ + u16 u16_ssTchRxBaselineAddr; /* /< Offset of SS touch sense baseline + * frame */ + + u16 u16_keyRawAddr; /* /< Offset of key raw frame */ + u16 u16_keyFilterAddr; /* /< Offset of key filter frame */ + u16 u16_keyStrenAddr; /* /< Offset of key strength frame */ + u16 u16_keyBaselineAddr; /* /< Offset of key baseline frame */ + + u16 u16_frcRawAddr; /* /< Offset of force touch raw frame */ + u16 u16_frcFilterAddr; /* /< Offset of force touch filter frame */ + u16 u16_frcStrenAddr; /* /< Offset of force touch strength frame */ + u16 u16_frcBaselineAddr;/* /< Offset of force touch baseline frame */ + + u16 u16_ssHvrTxRawAddr; /* /< Offset of SS hover Force raw frame */ + u16 u16_ssHvrTxFilterAddr; /* /< Offset of SS hover Force filter + * frame */ + u16 u16_ssHvrTxStrenAddr;/* /< Offset of SS hover Force strength frame + * */ + u16 u16_ssHvrTxBaselineAddr; /* /< Offset of SS hover Force baseline + * frame */ + + u16 u16_ssHvrRxRawAddr; /* /< Offset of SS hover Sense raw frame */ + u16 u16_ssHvrRxFilterAddr; /* /< Offset of SS hover Sense filter + * frame */ + u16 u16_ssHvrRxStrenAddr; /* /< Offset of SS hover Sense strength + * frame */ + u16 u16_ssHvrRxBaselineAddr; /* /< Offset of SS hover Sense baseline + * frame */ + + u16 u16_ssPrxTxRawAddr; /* /< Offset of SS proximity force raw frame */ + u16 u16_ssPrxTxFilterAddr; /* /< Offset of SS proximity force + * filter frame */ + u16 u16_ssPrxTxStrenAddr;/* /< Offset of SS proximity force strength + * frame */ + u16 u16_ssPrxTxBaselineAddr; /* /< Offset of SS proximity force + * baseline frame */ + + u16 u16_ssPrxRxRawAddr; /* /< Offset of SS proximity sense raw frame */ + u16 u16_ssPrxRxFilterAddr; /* /< Offset of SS proximity sense + * filter frame */ + u16 u16_ssPrxRxStrenAddr;/* /< Offset of SS proximity sense strength + * frame */ + u16 u16_ssPrxRxBaselineAddr; /* /< Offset of SS proximity sense + * baseline frame */ + + u16 u16_ssDetRawAddr; /* /< Offset of SS detect raw frame */ + u16 u16_ssDetFilterAddr; /* /< Offset of SS detect filter + * frame */ + u16 u16_ssDetStrenAddr; /* /< Offset of SS detect strength + * frame */ + u16 u16_ssDetBaselineAddr; /* /< Offset of SS detect baseline + * frame */ +} SysInfo; + +/** @}*/ + +int initCore(struct fts_ts_info *info); +void setResetGpio(int gpio); +int fts_system_reset(void); +int isSystemResettedUp(void); +int isSystemResettedDown(void); +void setSystemResetedUp(int val); +void setSystemResetedDown(int val); +int pollForEvent(int *event_to_search, int event_bytes, u8 *readData, int + time_to_wait); +int checkEcho(u8 *cmd, int size); +int setScanMode(u8 mode, u8 settings); +int setFeatures(u8 feat, u8 *settings, int size); +int defaultSysInfo(int i2cError); +int writeSysCmd(u8 sys_cmd, u8 *sett, int size); +int readSysInfo(int request); +int readConfig(u16 offset, u8 *outBuf, int len); +int writeConfig(u16 offset, u8 *data, int len); +int fts_enableInterrupt(bool enable); +int fts_crc_check(void); +int requestSyncFrame(u8 type); +int setActiveScanFrequency(u32 freq); +int writeHostDataMemory(u8 type, u8 *data, u8 msForceLen, u8 msSenseLen, + u8 ssForceLen, u8 ssSenseLen, int save); +int saveMpFlag(u8 mpflag); +#endif /* FTS_CORE_H */ diff --git a/fts_lib/ftsError.c b/fts_lib/ftsError.c new file mode 100644 index 0000000..2ae4337 --- /dev/null +++ b/fts_lib/ftsError.c @@ -0,0 +1,349 @@ +/* + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS error/info kernel log reporting * + * * + ************************************************************************** + ************************************************************************** + * + */ + +/*! + * \file ftsError.c + * \brief Contains all the function which handle with Error conditions + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/delay.h> + + +#include "../fts.h" +#include "ftsCore.h" +#include "ftsError.h" +#include "ftsIO.h" +#include "ftsTool.h" +#include "ftsCompensation.h" + + +static ErrorList errors;/* /< private variable which implement the Error List */ + +/** + * Check if an error code is related to an I2C failure + * @param error error code to check + * @return 1 if the first level error code is I2C related otherwise 0 + */ +int isI2cError(int error) +{ + if (((error & 0x000000FF) >= (ERROR_BUS_R & 0x000000FF)) && + ((error & 0x000000FF) <= (ERROR_BUS_O & 0x000000FF))) + return 1; + else + return 0; +} + + +/** + * Dump in the kernel log some debug info in case of FW hang + * @param outBuf (optional)pointer to bytes array where to copy the debug info, + * if NULL the data will just printed on the kernel log + * @param size dimension in bytes of outBuf, + * if > ERROR_DUMP_ROW_SIZE*ERROR_DUMP_COL_SIZE, only the first + * ERROR_DUMP_ROW_SIZE*ERROR_DUMP_COL_SIZE bytes will be copied + * @return OK if success or an error code which specify the type of error + */ +int dumpErrorInfo(u8 *outBuf, int size) +{ + int ret, i; + u8 data[ERROR_DUMP_ROW_SIZE * ERROR_DUMP_COL_SIZE] = { 0 }; + u32 sign = 0; + + pr_err("%s: Starting dump of error info...\n", __func__); + + ret = fts_writeReadU8UX(FTS_CMD_FRAMEBUFFER_R, BITS_16, ADDR_ERROR_DUMP, + data, ERROR_DUMP_ROW_SIZE * ERROR_DUMP_COL_SIZE, + DUMMY_FRAMEBUFFER); + if (ret < OK) { + pr_err("%s: reading data ERROR %08X\n", __func__, + ret); + return ret; + } else { + int buff_len, index = 0; + char *buff; + + buff_len = (2 + 1) * ERROR_DUMP_COL_SIZE + 1; + buff = kzalloc(buff_len, GFP_KERNEL); + if (buff == NULL) { + pr_err("%s: fail to allocate buffer\n", __func__); + return -ENOMEM; + } + + if (outBuf != NULL) { + sign = size > ERROR_DUMP_ROW_SIZE * + ERROR_DUMP_COL_SIZE ? ERROR_DUMP_ROW_SIZE * + ERROR_DUMP_COL_SIZE : size; + memcpy(outBuf, data, sign); + pr_err("%s: error info copied in the buffer!\n", + __func__); + } + pr_err("%s: Error Info =\n", __func__); + u8ToU32(data, &sign); + if (sign != ERROR_DUMP_SIGNATURE) + pr_err("%s: Wrong Error Signature! Data may be invalid!\n", + __func__); + else + pr_err("%s: Error Signature OK! Data are valid!\n", + __func__); + + for (i = 0; i < ERROR_DUMP_ROW_SIZE * ERROR_DUMP_COL_SIZE; + i++) { + index += scnprintf(buff + index, buff_len - index, + "%02X ", data[i]); + if (i % ERROR_DUMP_COL_SIZE == + (ERROR_DUMP_COL_SIZE - 1)) { + pr_err("%s: %d) %s\n", __func__, + i / ERROR_DUMP_COL_SIZE, + buff); + index = 0; + } + } + + kfree(buff); + pr_err("%s: dump of error info FINISHED!\n", __func__); + return OK; + } +} + + +/** + * Implement recovery strategies to be used when an error event is found + * while polling the FIFO + * @param event error event found during the polling + * @param size size of event + * @return OK if the error event doesn't require any action or the recovery + * strategy doesn't have any impact in the possible procedure that trigger the + * error, + * otherwise return an error code which specify the kind of error. + * If ERROR_HANDLER_STOP_PROC the calling function must stop! + */ +int errorHandler(u8 *event, int size) +{ + int res = OK; + struct fts_ts_info *info = NULL; + + if (getDev() != NULL) + info = dev_get_drvdata(getDev()); + + if (info != NULL && event != NULL && size > 1 && + event[0] == EVT_ID_ERROR) { + pr_debug("errorHandler: Starting handling...\n"); + addErrorIntoList(event, size); + switch (event[1]) { /* TODO: write an error log for + * undefined command subtype 0xBA */ + case EVT_TYPE_ERROR_ESD: /* esd */ + res = fts_chip_powercycle(info); + if (res < OK) + pr_err("errorHandler: Error performing powercycle ERROR %08X\n", + res); + + res = fts_system_reset(); + if (res < OK) + pr_err("errorHandler: Cannot reset the device ERROR %08X\n", + res); + res = (ERROR_HANDLER_STOP_PROC | res); + break; + + case EVT_TYPE_ERROR_WATCHDOG: /* watchdog */ + dumpErrorInfo(NULL, 0); + res = fts_system_reset(); + if (res < OK) + pr_err("errorHandler: Cannot reset the device ERROR %08X\n", + res); + res = (ERROR_HANDLER_STOP_PROC | res); + break; + + case EVT_TYPE_ERROR_ITO_FORCETOGND: + pr_err("errorHandler: Force Short to GND!\n"); + break; + case EVT_TYPE_ERROR_ITO_SENSETOGND: + pr_err("errorHandler: Sense short to GND!\n"); + break; + case EVT_TYPE_ERROR_ITO_FORCETOVDD: + pr_err("errorHandler: Force short to VDD!\n"); + break; + case EVT_TYPE_ERROR_ITO_SENSETOVDD: + pr_err("errorHandler: Sense short to VDD!\n"); + break; + case EVT_TYPE_ERROR_ITO_FORCE_P2P: + pr_err("errorHandler: Force Pin to Pin Short!\n"); + break; + case EVT_TYPE_ERROR_ITO_SENSE_P2P: + pr_err("errorHandler: Sense Pin to Pin Short!\n"); + break; + case EVT_TYPE_ERROR_ITO_FORCEOPEN: + pr_err("errorHandler: Force Open !\n"); + break; + case EVT_TYPE_ERROR_ITO_SENSEOPEN: + pr_err("errorHandler: Sense Open !\n"); + break; + case EVT_TYPE_ERROR_ITO_KEYOPEN: + pr_err("errorHandler: Key Open !\n"); + break; + + case EVT_TYPE_ERROR_FLASH_FAILED: + pr_err("errorHandler: Previous flash failed!\n"); + info->reflash_fw = 1; + break; + + default: + pr_debug("errorHandler: No Action taken!\n"); + break; + } + pr_debug("errorHandler: handling Finished! res = %08X\n", + res); + return res; + } else { + pr_err("errorHandler: event Null or not correct size! ERROR %08X\n", + ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } +} + + +/** + * Add an error event into the Error List + * @param event error event to add + * @param size size of event + * @return OK + */ +int addErrorIntoList(u8 *event, int size) +{ + int i = 0; + + pr_debug("Adding error in to ErrorList...\n"); + + memcpy(&errors.list[errors.last_index * FIFO_EVENT_SIZE], event, size); + i = FIFO_EVENT_SIZE - size; + if (i > 0) { + pr_info("Filling last %d bytes of the event with zero...\n", i); + memset(&errors.list[errors.last_index * FIFO_EVENT_SIZE + size], + 0, i); + } + pr_debug("Adding error in to ErrorList... FINISHED!\n"); + + errors.count += 1; + if (errors.count > FIFO_DEPTH) + pr_err("ErrorList is going in overflow... the first %d event(s) were override!\n", + errors.count - FIFO_DEPTH); + errors.last_index = (errors.last_index + 1) % FIFO_DEPTH; + + return OK; +} + +/** + * Reset the Error List setting the count and last_index to 0. + * @return OK + */ +int resetErrorList(void) +{ + errors.count = 0; + errors.last_index = 0; + memset(errors.list, 0, FIFO_DEPTH * FIFO_EVENT_SIZE); + /* if count is not considered is better reset also the list in order to + * avoid to read data previously copied into the list */ + return OK; +} + +/** + * Get the number of error events copied into the Error List + * @return the number of error events into the Error List + */ +int getErrorListCount(void) +{ + if (errors.count > FIFO_DEPTH) + return FIFO_DEPTH; + else + return errors.count; +} + +/* in case of success return the index of the event found */ +/** + * Scroll the Error List looking for the event specified + * @param event_to_search event_to_search pointer to an array of int where + * each element correspond to a byte of the event to find. If the element + * of the array has value -1, the byte of the event, in the same position + * of the element is ignored. + * @param event_bytes size of event_to_search + * @return a value >=0 if the event is found which represent the index of + * the Error List where the event is located otherwise an error code + */ +int pollErrorList(int *event_to_search, int event_bytes) +{ + int i = 0, j = 0, find = 0; + int count = getErrorListCount(); + + pr_debug("Starting to poll ErrorList...\n"); + while (find != 1 && i < count) { + find = 1; + for (j = 0; j < event_bytes; j++) { + if ((event_to_search[i] != -1) && + ((int)errors.list[i * FIFO_EVENT_SIZE + j] != + event_to_search[i])) { + find = 0; + break; + } + } + i++; + } + if (find == 1) { + pr_debug("Error Found into ErrorList!\n"); + return i - 1; /* there is i++ at the end of the while */ + } else { + pr_err("Error Not Found into ErrorList! ERROR %08X\n", + ERROR_TIMEOUT); + return ERROR_TIMEOUT; + } +} + + + +/** + * Poll the Error List looking for any error types passed in the arguments. + * Return at the first match! + * @param list pointer to a list of error types to look for + * @param size size of list + * @return error type found if success or ERROR_TIMEOUT + */ +int pollForErrorType(u8 *list, int size) +{ + int i = 0, j = 0, find = 0; + int count = getErrorListCount(); + + pr_info("%s: Starting to poll ErrorList... count = %d\n", + __func__, count); + while (find != 1 && i < count) { + for (j = 0; j < size; j++) { + if (list[j] == errors.list[i * FIFO_EVENT_SIZE + 1]) { + find = 1; + break; + } + } + i++; + } + if (find == 1) { + pr_info("%s: Error Type %02X into ErrorList!\n", + __func__, list[j]); + return list[j]; + } else { + pr_err("%s: Error Type Not Found into ErrorList! ERROR %08X\n", + __func__, ERROR_TIMEOUT); + return ERROR_TIMEOUT; + } +} diff --git a/fts_lib/ftsError.h b/fts_lib/ftsError.h new file mode 100644 index 0000000..d3a539f --- /dev/null +++ b/fts_lib/ftsError.h @@ -0,0 +1,236 @@ +/* + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS error/info kernel log reporting * + * * + ************************************************************************** + ************************************************************************** + * + */ + +/*! + * \file ftsError.h + * \brief Contains all the definitions and structs which refer to Error + * conditions + */ + +#ifndef FTS_ERROR_H +#define FTS_ERROR_H + +#include "ftsHardware.h" +#include "ftsSoftware.h" + + +/** @defgroup error_codes Error Codes + * Error codes that can be reported by the driver functions. + * An error code is made up by 4 bytes, each byte indicate a logic error + * level.\n + * From the LSB to the MSB, the logic level increase going from a low level + * error (I2C,TIMEOUT) to an high level error (flashing procedure fail, + * production test fail etc) + * @{ + */ + +/* FIRST LEVEL ERROR CODE */ +/** @defgroup first_level First Level Error Code + * @ingroup error_codes + * Errors related to low level operation which are not under control of driver, + * such as: communication protocol (I2C/SPI), timeout, file operations ... + * @{ + */ +#define OK (0x00000000) /* /< No ERROR */ +#define ERROR_ALLOC (0x80000001) /* /< allocation of memory + * failed */ +#define ERROR_BUS_R (0x80000002) /* /< i2c/spi read failed */ +#define ERROR_BUS_W (0x80000003) /* /< i2c/spi write failed */ +#define ERROR_BUS_WR (0x80000004) /* /< i2c/spi write/read + * failed */ +#define ERROR_BUS_O (0x80000005) /* /< error during + * opening an i2c device */ +#define ERROR_OP_NOT_ALLOW (0x80000006) /* /< operation not allowed */ +#define ERROR_TIMEOUT (0x80000007) /* /< timeout expired! + * exceed the max number + * of retries or the max + * waiting time */ +#define ERROR_FILE_NOT_FOUND (0x80000008) /* /< the file that i + * want to open is not found */ +#define ERROR_FILE_PARSE (0x80000009) /* /< error during parsing + * the file */ +#define ERROR_FILE_READ (0x8000000A) /* /< error during + * reading the file */ +#define ERROR_LABEL_NOT_FOUND (0x8000000B) /* /< label not found */ +#define ERROR_FW_NO_UPDATE (0x8000000C) /* /< fw in the chip + * newer than the one in + * the memmh */ +#define ERROR_FLASH_UNKNOWN (0x8000000D) /* /< flash status busy + * or unknown */ +/** @}*/ + +/* SECOND LEVEL ERROR CODE */ +/** @defgroup second_level Second Level Error Code + * @ingroup error_codes + * Errors related to simple logic operations in the IC which require one + * command or which are part of a more complex procedure + * @{ + */ +#define ERROR_DISABLE_INTER (0x80000200) /* /< unable to + * disable the + * interrupt */ +#define ERROR_ENABLE_INTER (0x80000300) /* /< unable to activate + * the interrupt */ +#define ERROR_READ_CONFIG (0x80000400) /* /< failed to read + * config memory */ +#define ERROR_GET_OFFSET (0x80000500) /* /< unable to + * read an offset from + * memory */ +#define ERROR_GET_FRAME_DATA (0x80000600) /* /< unable to + * retrieve the data of + * a required frame */ +#define ERROR_DIFF_DATA_TYPE (0x80000700) /* /< FW answers + * with an event that + * has a different + * address respect the + * request done */ +#define ERROR_WRONG_DATA_SIGN (0x80000800) /* /< the signature of + * the host data is not + * HEADER_SIGNATURE */ +#define ERROR_SET_SCAN_MODE_FAIL (0x80000900) /* /< setting + * the scanning mode + * failed + * (sense on/off + * etc...) */ +#define ERROR_SET_FEATURE_FAIL (0x80000A00) /* /< setting a specific + * feature failed */ +#define ERROR_SYSTEM_RESET_FAIL (0x80000B00) /* /< the command + * SYSTEM RESET + * failed */ +#define ERROR_FLASH_NOT_READY (0x80000C00) /* /< flash status not + * ready within a + * timeout */ +#define ERROR_FW_VER_READ (0x80000D00) /* /< unable to retrieve + * fw_vers or the + * config_id */ +#define ERROR_GESTURE_ENABLE_FAIL (0x80000E00) /* /< unable to + * enable/disable + * the gesture */ +#define ERROR_GESTURE_START_ADD (0x80000F00) /* /< unable to start to + * add custom gesture */ +#define ERROR_GESTURE_FINISH_ADD (0x80001000) /* /< unable to finish + * to add custom gesture + */ +#define ERROR_GESTURE_DATA_ADD (0x80001100) /* /< unable to add + * custom gesture data + * */ +#define ERROR_GESTURE_REMOVE (0x80001200) /* /< unable to remove + * custom gesture data + * */ +#define ERROR_FEATURE_ENABLE_DISABLE (0x80001300) /* /< unable to + * enable/disable a + * feature mode in + * the IC */ +#define ERROR_NOISE_PARAMETERS (0x80001400) /* /< unable to set/read + * noise parameter in + * the IC */ +#define ERROR_CH_LEN (0x80001500) /* /< unable to retrieve + * the force and/or + * sense length */ +#define ERROR_INFO_BLOCK (0x80001600) /* /< info + * block + * corruption */ +/** @}*/ + +/* THIRD LEVEL ERROR CODE */ +/** @defgroup third_level Third Level Error Code + * @ingroup error_codes + * Errors related to logic operations in the IC which require more + * commands/steps or which are part of a more complex procedure + * @{ + */ +#define ERROR_REQU_HDM_DOWNLOAD (0x80010000) /* /< HDM download + * request failed */ +#define ERROR_REQU_DATA (0x80020000) /* /< data request + * failed */ +#define ERROR_HDM_DATA_HEADER (0x80030000) /* /< unable to retrieve + * the HDM data + * header */ +#define ERROR_COMP_DATA_GLOBAL (0x80040000) /* /< unable to retrieve + * the global + * compensation data */ +#define ERROR_COMP_DATA_NODE (0x80050000) /* /< unable to retrieve + * the compensation data + * for each node */ +#define ERROR_TEST_CHECK_FAIL (0x80060000) /* /< check of + * production limits or + * of fw answers failed */ +#define ERROR_MEMH_READ (0x80070000) /* /< memh reading + * failed */ +#define ERROR_FLASH_BURN_FAILED (0x80080000) /* /< flash burn failed */ +#define ERROR_MS_TUNING (0x80090000) /* /< ms tuning failed */ +#define ERROR_SS_TUNING (0x800A0000) /* /< ss tuning failed */ +#define ERROR_LP_TIMER_TUNING (0x800B0000) /* /< lp timer + * calibration failed */ +#define ERROR_SAVE_CX_TUNING (0x800C0000) /* /< save cx data to + * flash failed */ +#define ERROR_HANDLER_STOP_PROC (0x800D0000) /* /< stop the poll of the FIFO + * if particular errors are + * found */ +#define ERROR_CHECK_ECHO_FAIL (0x800E0000) /* /< unable to retrieve + * echo event */ +#define ERROR_GET_FRAME (0x800F0000) /* /< unable to get frame */ +#define ERROR_GOLDEN_MUTUAL_DATA (0x80100000) /* /< unable to read Golden + * Mutual Data */ +/** @}*/ + +/* FOURTH LEVEL ERROR CODE */ +/** @defgroup fourth_level Fourth Level Error Code + * @ingroup error_codes + * Errors related to the highest logic operations in the IC which have an + * important impact on the driver flow or which require several commands and + * steps to be executed + * @{ + */ +#define ERROR_PROD_TEST_DATA (0x81000000) /* /< production data + * test failed */ +#define ERROR_FLASH_PROCEDURE (0x82000000) /* /< fw update + * procedure failed */ +#define ERROR_PROD_TEST_ITO (0x83000000) /* /< production + * ito test failed */ +#define ERROR_PROD_TEST_INITIALIZATION (0x84000000) /* /< production + * initialization + * test failed */ +#define ERROR_GET_INIT_STATUS (0x85000000) /* /< mismatch of the MS or + * SS tuning_version */ +/** @}*/ + +/** @}*/ /* end of error_commands section */ + +/** + * Struct which store an ordered list of the errors events encountered during + *the polling of a FIFO. + * The max number of error events that can be stored is equal to FIFO_DEPTH + */ +typedef struct { + u8 list[FIFO_DEPTH * FIFO_EVENT_SIZE]; /* /< byte array which contains + * the series of error events + * encountered from the last + * reset of the list. */ + int count; /* /< number of error events stored in the list */ + int last_index; /* /< index of the list where will be stored the next + * error event. Subtract -1 to have the index of the + * last error event! */ +} ErrorList; + +int isI2cError(int error); +int dumpErrorInfo(u8 *outBuf, int size); +int errorHandler(u8 *event, int size); +int addErrorIntoList(u8 *event, int size); +int getErrorListCount(void); +int resetErrorList(void); +int pollErrorList(int *event_to_search, int event_bytes); +int pollForErrorType(u8 *list, int size); +#endif diff --git a/fts_lib/ftsFlash.c b/fts_lib/ftsFlash.c new file mode 100644 index 0000000..f57de07 --- /dev/null +++ b/fts_lib/ftsFlash.c @@ -0,0 +1,1123 @@ +/* + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS API for Flashing the IC * + * * + ************************************************************************** + ************************************************************************** + * + */ + + +/*! + * \file ftsFlash.c + * \brief Contains all the functions to handle the FW update process + */ + +#include "ftsCore.h" +#include "ftsCompensation.h" +#include "ftsError.h" +#include "ftsFlash.h" +#include "ftsFrame.h" +#include "ftsIO.h" +#include "ftsSoftware.h" +#include "ftsTest.h" +#include "ftsTime.h" +#include "ftsTool.h" +#include "../fts.h" /* needed for including the define FW_H_FILE */ + + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <stdarg.h> +#include <linux/serio.h> +#include <linux/time.h> +#include <linux/delay.h> +#include <linux/ctype.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <linux/firmware.h> + + +#ifdef FW_H_FILE +#include "../fts_fw.h" +#endif + + +extern SysInfo systemInfo; /* /< forward declaration of the global variable + * of containing System Info Data */ + + +/** + * Retrieve the actual FW data from the system (bin file or header file) + * @param pathToFile name of FW file to load or "NULL" if the FW data should be + * loaded by a .h file + * @param data pointer to the pointer which will contains the FW data + * @param size pointer to a variable which will contain the size of the loaded + * data + * @return OK if success or an error code which specify the type of error + */ +int getFWdata(const char *pathToFile, u8 **data, int *size) +{ + const struct firmware *fw = NULL; + struct device *dev = getDev(); + struct fts_ts_info *info = NULL; + int res, from = 0; + char *path = (char *)pathToFile; + + if (dev != NULL) + info = dev_get_drvdata(dev); + + pr_info("getFWdata starting ...\n"); + if (strncmp(pathToFile, "NULL", 4) == 0) { + from = 1; + if (info != NULL && info->board) + path = (char *)info->board->fw_name; + else + path = PATH_FILE_FW; + } + /* keep the switch case because if the argument passed is null but + * the option from .h is not set we still try to load from bin */ + switch (from) { +#ifdef FW_H_FILE + case 1: + pr_info("Read FW from .h file!\n"); + *size = FW_SIZE_NAME; + *data = (u8 *)kmalloc((*size) * sizeof(u8), GFP_KERNEL); + if (*data == NULL) { + pr_err("getFWdata: Impossible to allocate memory! ERROR %08X\n", + ERROR_ALLOC); + return ERROR_ALLOC; + } + memcpy(*data, (u8 *)FW_ARRAY_NAME, (*size)); + + break; +#endif + default: + pr_info("Read FW from BIN file %s !\n", path); + + if (dev != NULL) { + res = request_firmware(&fw, path, dev); + if (res == 0) { + *size = fw->size; + *data = (u8 *)kmalloc((*size) * sizeof(u8), + GFP_KERNEL); + if (*data == NULL) { + pr_err("getFWdata: Impossible to allocate memory! ERROR %08X\n", + ERROR_ALLOC); + release_firmware(fw); + return ERROR_ALLOC; + } + memcpy(*data, (u8 *)fw->data, (*size)); + release_firmware(fw); + } else { + pr_err("getFWdata: No File found! ERROR %08X\n", + ERROR_FILE_NOT_FOUND); + return ERROR_FILE_NOT_FOUND; + } + } else { + pr_err("getFWdata: No device found! ERROR %08X\n", + ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + } + + pr_info("getFWdata Finished!\n"); + return OK; +} + + +/** + * Perform all the steps to read the FW that should be burnt in the IC from + * the system and parse it in order to fill a Firmware struct with the relevant + * info + * @param path name of FW file to load or "NULL" if the FW data should be + * loaded by a .h file + * @param fw pointer to a Firmware variable which will contains the FW data and + * info + * @param keep_cx if 1, the CX area will be loaded otherwise will be skipped + * @return OK if success or an error code which specify the type of error + */ +int readFwFile(const char *path, Firmware *fw, int keep_cx) +{ + int res; + int orig_size; + u8 *orig_data = NULL; + + + res = getFWdata(path, &orig_data, &orig_size); + if (res < OK) { + pr_err("readFwFile: impossible retrieve FW... ERROR %08X\n", + ERROR_MEMH_READ); + return res | ERROR_MEMH_READ; + } + res = parseBinFile(orig_data, orig_size, fw, keep_cx); + if (res < OK) { + pr_err("readFwFile: impossible parse ERROR %08X\n", + ERROR_MEMH_READ); + return res | ERROR_MEMH_READ; + } + + return OK; +} + +/** + * Perform all the steps necessary to burn the FW into the IC + * @param path name of FW file to load or "NULL" if the FW data should be + * loaded by a .h file + * @param force if 1, the flashing procedure will be forced and executed + * regardless the additional info, otherwise the FW in the file will be burnt + * only if it is newer than the one running in the IC + * @param keep_cx if 1, the CX area will be loaded and burnt otherwise will be + * skipped and the area will be untouched + * @return OK if success or an error code which specify the type of error + */ +int flashProcedure(const char *path, int force, int keep_cx) +{ + Firmware fw; + int res; + + fw.data = NULL; + pr_info("Reading Fw file...\n"); + res = readFwFile(path, &fw, keep_cx); + if (res < OK) { + pr_err("flashProcedure: ERROR %08X\n", + (res | ERROR_FLASH_PROCEDURE)); + kfree(fw.data); + return res | ERROR_FLASH_PROCEDURE; + } + pr_info("Fw file read COMPLETED!\n"); + + pr_info("Starting flashing procedure...\n"); + res = flash_burn(fw, force, keep_cx); + if (res < OK && res != (ERROR_FW_NO_UPDATE | ERROR_FLASH_BURN_FAILED)) { + pr_err("flashProcedure: ERROR %08X\n", + ERROR_FLASH_PROCEDURE); + kfree(fw.data); + return res | ERROR_FLASH_PROCEDURE; + } + pr_info("flashing procedure Finished!\n"); + kfree(fw.data); + + return res; +} + +/** + * Poll the Flash Status Registers after the execution of a command to check + * if the Flash becomes ready within a timeout + * @param type register to check according to the previous command sent + * @return OK if success or an error code which specify the type of error + */ +int wait_for_flash_ready(u8 type) +{ + u8 cmd[5] = { FTS_CMD_HW_REG_R, 0x20, 0x00, 0x00, type }; + + u8 readData[2] = { 0 }; + int i, res = -1; + + pr_info("Waiting for flash ready ...\n"); + for (i = 0; i < FLASH_RETRY_COUNT && res != 0; i++) { + res = fts_writeRead(cmd, ARRAY_SIZE(cmd), readData, 2); + if (res < OK) + pr_err("wait_for_flash_ready: ERROR %08X\n", + ERROR_BUS_W); + else { +#ifdef I2C_INTERFACE /* in case of spi there is a dummy byte */ + res = readData[0] & 0x80; +#else + res = readData[1] & 0x80; +#endif + + pr_info("flash status = %d\n", res); + } + mdelay(FLASH_WAIT_BEFORE_RETRY); + } + + if (i == FLASH_RETRY_COUNT && res != 0) { + pr_err("Wait for flash TIMEOUT! ERROR %08X\n", + ERROR_TIMEOUT); + return ERROR_TIMEOUT; + } + + pr_info("Flash READY!\n"); + return OK; +} + + +/** + * Put the M3 in hold + * @return OK if success or an error code which specify the type of error + */ +int hold_m3(void) +{ + int ret; + u8 cmd[1] = { 0x01 }; + + pr_info("Command m3 hold...\n"); + ret = fts_writeU8UX(FTS_CMD_HW_REG_W, ADDR_SIZE_HW_REG, + ADDR_SYSTEM_RESET, cmd, 1); + if (ret < OK) { + pr_err("%s: ERROR %08X\n", __func__, ret); + return ret; + } + pr_info("Hold M3 DONE!\n"); + +#if !defined(I2C_INTERFACE) + if (getClient() && + (getClient()->mode & SPI_3WIRE) == 0) { + /* configure manually SPI4 because when no fw is running the + * chip use SPI3 by default */ + pr_info("Setting SPI4 mode...\n"); + cmd[0] = 0x10; + ret = fts_writeU8UX(FTS_CMD_HW_REG_W, ADDR_SIZE_HW_REG, + ADDR_GPIO_DIRECTION, cmd, 1); + if (ret < OK) { + pr_err("%s: can not set gpio dir ERROR %08X\n", + __func__, ret); + return ret; + } + + cmd[0] = 0x02; + ret = fts_writeU8UX(FTS_CMD_HW_REG_W, ADDR_SIZE_HW_REG, + ADDR_GPIO_PULLUP, cmd, 1); + if (ret < OK) { + pr_err("%s: can not set gpio pull-up ERROR %08X\n", + __func__, ret); + return ret; + } + +#ifdef ALIX + cmd[0] = 0x70; + ret = fts_writeU8UX(FTS_CMD_HW_REG_W, ADDR_SIZE_HW_REG, + ADDR_GPIO_CONFIG_REG3, cmd, 1); + if (ret < OK) { + pr_err("%s: can not set gpio config ERROR %08X\n", + __func__, ret); + return ret; + } +#else + cmd[0] = 0x07; + ret = fts_writeU8UX(FTS_CMD_HW_REG_W, ADDR_SIZE_HW_REG, + ADDR_GPIO_CONFIG_REG2, cmd, 1); + if (ret < OK) { + pr_err("%s: can not set gpio config ERROR %08X\n", + __func__, ret); + return ret; + } +#endif + + cmd[0] = 0x30; + ret = fts_writeU8UX(FTS_CMD_HW_REG_W, ADDR_SIZE_HW_REG, + ADDR_GPIO_CONFIG_REG0, cmd, 1); + if (ret < OK) { + pr_err("%s: can not set gpio config ERROR %08X\n", + __func__, ret); + return ret; + } + + cmd[0] = SPI4_MASK; + ret = fts_writeU8UX(FTS_CMD_HW_REG_W, ADDR_SIZE_HW_REG, + ADDR_ICR, cmd, 1); + if (ret < OK) { + pr_err("%s: can not set spi4 mode ERROR %08X\n", + __func__, ret); + return ret; + } + mdelay(1); /* wait for the GPIO to stabilize */ + } +#endif + + return OK; +} + + + + +/** + * Parse the raw data read from a FW file in order to fill properly the fields + * of a Firmware variable + * @param fw_data raw FW data loaded from system + * @param fw_size size of fw_data + * @param fwData pointer to a Firmware variable which will contain the + * processed data + * @param keep_cx if 1, the CX area will be loaded and burnt otherwise will be + * skipped and the area will be untouched + * @return OK if success or an error code which specify the type of error + */ +int parseBinFile(u8 *fw_data, int fw_size, Firmware *fwData, int keep_cx) +{ + int dimension, index = 0; + u32 temp; + int res, i; + char buff[(2 + 1) * EXTERNAL_RELEASE_INFO_SIZE + 1]; + int buff_len = sizeof(buff); + int buff_index = 0; + + /* the file should contain at least the header plus the content_crc */ + if (fw_size < FW_HEADER_SIZE + FW_BYTES_ALIGN || fw_data == NULL) { + pr_err("parseBinFile: Read only %d instead of %d... ERROR %08X\n", + fw_size, FW_HEADER_SIZE + FW_BYTES_ALIGN, + ERROR_FILE_PARSE); + res = ERROR_FILE_PARSE; + goto END; + } else { + /* start parsing of bytes */ + u8ToU32(&fw_data[index], &temp); + if (temp != FW_HEADER_SIGNATURE) { + pr_err("parseBinFile: Wrong Signature %08X ... ERROR %08X\n", + temp, ERROR_FILE_PARSE); + res = ERROR_FILE_PARSE; + goto END; + } + pr_info("parseBinFile: Fw Signature OK!\n"); + index += FW_BYTES_ALIGN; + u8ToU32(&fw_data[index], &temp); + if (temp != FW_FTB_VER) { + pr_err("parseBinFile: Wrong ftb_version %08X ... ERROR %08X\n", + temp, ERROR_FILE_PARSE); + res = ERROR_FILE_PARSE; + goto END; + } + pr_info("parseBinFile: ftb_version OK!\n"); + index += FW_BYTES_ALIGN; + if (fw_data[index] != DCHIP_ID_0 || fw_data[index + 1] != + DCHIP_ID_1) { + pr_err("parseBinFile: Wrong target %02X != %02X %02X != %02X ... ERROR %08X\n", + fw_data[index], DCHIP_ID_0, + fw_data[index + 1], + DCHIP_ID_1, ERROR_FILE_PARSE); + res = ERROR_FILE_PARSE; + goto END; + } + index += FW_BYTES_ALIGN; + u8ToU32(&fw_data[index], &temp); + pr_info("parseBinFile: FILE SVN REV = %08X\n", temp); + + index += FW_BYTES_ALIGN; + u8ToU32(&fw_data[index], &temp); + fwData->fw_ver = temp; + pr_info("parseBinFile: FILE Fw Version = %04X\n", + fwData->fw_ver); + + index += FW_BYTES_ALIGN; + u8ToU32(&fw_data[index], &temp); + pr_info("parseBinFile: FILE Config Project ID = %08X\n", temp); + + index += FW_BYTES_ALIGN; + u8ToU32(&fw_data[index], &temp); + fwData->config_ver = temp; + pr_info("parseBinFile: FILE Config Version = %08X\n", + fwData->config_ver); + + index += FW_BYTES_ALIGN * 2; /* skip reserved data */ + + index += FW_BYTES_ALIGN; + for (i = 0; i < EXTERNAL_RELEASE_INFO_SIZE; i++) { + fwData->externalRelease[i] = fw_data[index++]; + buff_index += scnprintf(buff + buff_index, + buff_len - buff_index, + "%02X ", + fwData->externalRelease[i]); + } + pr_info("parseBinFile: File External Release = %s\n", buff); + + /* index+=FW_BYTES_ALIGN; */ + u8ToU32(&fw_data[index], &temp); + fwData->sec0_size = temp; + pr_info("parseBinFile: sec0_size = %08X (%d bytes)\n", + fwData->sec0_size, fwData->sec0_size); + + index += FW_BYTES_ALIGN; + u8ToU32(&fw_data[index], &temp); + fwData->sec1_size = temp; + pr_info("parseBinFile: sec1_size = %08X (%d bytes)\n", + fwData->sec1_size, fwData->sec1_size); + + index += FW_BYTES_ALIGN; + u8ToU32(&fw_data[index], &temp); + fwData->sec2_size = temp; + pr_info("parseBinFile: sec2_size = %08X (%d bytes)\n", + fwData->sec2_size, fwData->sec2_size); + + index += FW_BYTES_ALIGN; + u8ToU32(&fw_data[index], &temp); + fwData->sec3_size = temp; + pr_info("parseBinFile: sec3_size = %08X (%d bytes)\n", + fwData->sec3_size, fwData->sec3_size); + + index += FW_BYTES_ALIGN;/* skip header crc */ + + /* if (!keep_cx) */ + /* { */ + dimension = fwData->sec0_size + fwData->sec1_size + + fwData->sec2_size + fwData->sec3_size; + temp = fw_size; + /*} else + * { + * dimension = fwData->sec0_size + fwData->sec1_size; + * temp = fw_size - fwData->sec2_size - fwData->sec3_size; + * fwData->sec2_size = 0; + * fwData->sec3_size = 0; + * }*/ + + if (dimension + FW_HEADER_SIZE + FW_BYTES_ALIGN != temp) { + pr_err("parseBinFile: Read only %d instead of %d... ERROR %08X\n", + fw_size, dimension + FW_HEADER_SIZE + + FW_BYTES_ALIGN, ERROR_FILE_PARSE); + res = ERROR_FILE_PARSE; + goto END; + } + + fwData->data = (u8 *)kmalloc(dimension * sizeof(u8), + GFP_KERNEL); + if (fwData->data == NULL) { + pr_err("parseBinFile: ERROR %08X\n", ERROR_ALLOC); + res = ERROR_ALLOC; + goto END; + } + + index += FW_BYTES_ALIGN; + memcpy(fwData->data, &fw_data[index], dimension); + if (fwData->sec2_size != 0) + u8ToU16(&fwData->data[fwData->sec0_size + + fwData->sec1_size + + FW_CX_VERSION], &fwData->cx_ver); + + else { + pr_err("parseBinFile: Initialize cx_ver to default value!\n"); + fwData->cx_ver = systemInfo.u16_cxVer; + } + + pr_info("parseBinFile: CX Version = %04X\n", fwData->cx_ver); + + fwData->data_size = dimension; + index = FLASH_ORG_INFO_INDEX; + fwData->fw_code_size = fw_data[index++]; + fwData->panel_config_size = fw_data[index++]; + fwData->cx_area_size = fw_data[index++]; + fwData->fw_config_size = fw_data[index]; + + pr_info("parseBinFile: Code Pages: %d panel area Pages: %d" + " cx area Pages: %d fw config Pages: %d !\n", + fwData->fw_code_size, fwData->panel_config_size, + fwData->cx_area_size, fwData->fw_config_size); + + if ((fwData->fw_code_size == 0) || + (fwData->panel_config_size == 0) || + (fwData->cx_area_size == 0) || + (fwData->fw_config_size == 0)) { + pr_info("parseBinFile: Using default flash Address\n"); + fwData->code_start_addr = FLASH_ADDR_CODE; + fwData->cx_start_addr = FLASH_ADDR_CX; + fwData->config_start_addr = FLASH_ADDR_CONFIG; + } else { + fwData->code_start_addr = FLASH_ADDR_CODE; + fwData->cx_start_addr = (FLASH_ADDR_CODE + + (((fwData->fw_code_size + + fwData->panel_config_size) * + FLASH_PAGE_SIZE) / 4)); + fwData->config_start_addr = (FLASH_ADDR_CODE + + (((fwData->fw_code_size + + fwData->panel_config_size + + fwData->cx_area_size) * + FLASH_PAGE_SIZE) / 4)); + } + + pr_info("parseBinFile: Code start addr: 0x%08X" + " cx start addr: 0x%08X" + " fw start addr: 0x%08X !\n", + fwData->code_start_addr, fwData->cx_start_addr, + fwData->config_start_addr); + + pr_info("READ FW DONE %d bytes!\n", fwData->data_size); + res = OK; + goto END; + } + +END: + kfree(fw_data); + return res; +} + +/* + * Enable UVLO and Auto Power Down Mode + * @return OK if success or an error code which specify the type of error + */ +int flash_enable_uvlo_autopowerdown(void) +{ + u8 cmd[6] = { FTS_CMD_HW_REG_W, 0x20, 0x00, 0x00, + FLASH_UVLO_ENABLE_CODE0, + FLASH_UVLO_ENABLE_CODE1 }; + u8 cmd1[6] = { FTS_CMD_HW_REG_W, 0x20, 0x00, 0x00, + FLASH_AUTOPOWERDOWN_ENABLE_CODE0, + FLASH_AUTOPOWERDOWN_ENABLE_CODE1 }; + + pr_info("Command enable uvlo ...\n"); + if (fts_write(cmd, ARRAY_SIZE(cmd)) < OK) { + pr_err("%s: ERROR %08X\n", __func__, ERROR_BUS_W); + return ERROR_BUS_W; + } + if (fts_write(cmd1, ARRAY_SIZE(cmd1)) < OK) { + pr_err("%s: ERROR %08X\n", __func__, ERROR_BUS_W); + return ERROR_BUS_W; + } + + pr_info("Enable uvlo and flash auto power down DONE!\n"); + + return OK; +} + +/** + * Unlock the flash to be programmed + * @return OK if success or an error code which specify the type of error + */ +int flash_unlock(void) +{ + u8 cmd[6] = { FTS_CMD_HW_REG_W, 0x20, 0x00, 0x00, + FLASH_UNLOCK_CODE0, FLASH_UNLOCK_CODE1 }; + + u8 cmd1[6] = { FTS_CMD_HW_REG_W, 0x20, 0x00, 0x00, + FLASH_UNLOCK_CODE2, FLASH_UNLOCK_CODE3 }; + + pr_info("Command unlock ...\n"); + if (fts_write(cmd, ARRAY_SIZE(cmd)) < OK) { + pr_err("flash_unlock: ERROR %08X\n", ERROR_BUS_W); + return ERROR_BUS_W; + } + + if (fts_write(cmd1, ARRAY_SIZE(cmd1)) < OK) { + pr_err("Command unlock: ERROR %08X\n", ERROR_BUS_W); + return ERROR_BUS_W; + } + + pr_info("Unlock flash DONE!\n"); + + return OK; +} + +/** + * Unlock the flash to be erased + * @return OK if success or an error code which specify the type of error + */ +int flash_erase_unlock(void) +{ + u8 cmd[6] = { FTS_CMD_HW_REG_W, 0x20, 0x00, + 0x00, + FLASH_ERASE_UNLOCK_CODE0, FLASH_ERASE_UNLOCK_CODE1 }; + + pr_info("Try to erase unlock flash...\n"); + + pr_info("Command erase unlock ...\n"); + if (fts_write(cmd, ARRAY_SIZE(cmd)) < 0) { + pr_err("flash_erase_unlock: ERROR %08X\n", ERROR_BUS_W); + return ERROR_BUS_W; + } + + pr_info("Erase Unlock flash DONE!\n"); + + return OK; +} + +/** + * Erase the full flash + * @return OK if success or an error code which specify the type of error + */ +int flash_full_erase(void) +{ + int status; + + u8 cmd1[6] = { FTS_CMD_HW_REG_W, 0x20, 0x00, 0x00, + FLASH_ERASE_CODE0 + 1, 0x00 }; + u8 cmd[6] = { FTS_CMD_HW_REG_W, 0x20, 0x00, 0x00, + FLASH_ERASE_CODE0, FLASH_ERASE_CODE1 }; + + if (fts_write(cmd1, ARRAY_SIZE(cmd1)) < OK) { + pr_err("flash_erase_page_by_page: ERROR %08X\n", ERROR_BUS_W); + return ERROR_BUS_W; + } + + pr_info("Command full erase sent ...\n"); + if (fts_write(cmd, ARRAY_SIZE(cmd)) < OK) { + pr_err("flash_full_erase: ERROR %08X\n", ERROR_BUS_W); + return ERROR_BUS_W; + } + + status = wait_for_flash_ready(FLASH_ERASE_CODE0); + + if (status != OK) { + pr_err("flash_full_erase: ERROR %08X\n", ERROR_FLASH_NOT_READY); + return status | ERROR_FLASH_NOT_READY; + /* Flash not ready within the chosen time, better exit! */ + } + + pr_info("Full Erase flash DONE!\n"); + + return OK; +} + +/** + * Erase the flash page by page, giving the possibility to skip the CX area and + * maintain therefore its value + * @param keep_cx if SKIP_PANEL_INIT the Panel Init pages will be skipped, + * if > SKIP_PANEL_CX_INIT Cx and Panel Init pages otherwise all the pages will + * be deleted + * @return OK if success or an error code which specify the type of error + */ +int flash_erase_page_by_page(ErasePage keep_cx, Firmware *fw) +{ + u8 status, i = 0; + + u8 flash_cx_start_page = FLASH_CX_PAGE_START; + u8 flash_cx_end_page = FLASH_CX_PAGE_END; + u8 flash_panel_start_page = FLASH_PANEL_PAGE_START; + u8 flash_panel_end_page = FLASH_PANEL_PAGE_END; + + u8 cmd1[6] = { FTS_CMD_HW_REG_W, 0x20, 0x00, 0x00, + FLASH_ERASE_CODE0 + 1, 0x00 }; + u8 cmd[6] = { FTS_CMD_HW_REG_W, 0x20, 0x00, 0x00, FLASH_ERASE_CODE0, + 0xA0 }; + u8 cmd2[9] = { FTS_CMD_HW_REG_W, 0x20, 0x00, + 0x01, 0x28, + 0xFF, + 0xFF, 0xFF, 0xFF }; + u8 mask[4] = { 0 }; + char buff[(2 + 1) * 4 + 1]; + int buff_len = sizeof(buff); + int index = 0; + + if ((fw->fw_code_size == 0) || + (fw->panel_config_size == 0) || + (fw->cx_area_size == 0) || (fw->fw_config_size == 0)) { + pr_info(" using default page address!\n"); + } else { + flash_panel_start_page = fw->fw_code_size; + if (fw->panel_config_size > 1) + flash_panel_end_page = flash_panel_start_page + + (fw->panel_config_size - 1); + else + flash_panel_end_page = flash_panel_start_page; + + flash_cx_start_page = flash_panel_end_page + 1; + if (fw->cx_area_size > 1) + flash_cx_end_page = flash_cx_start_page + + (fw->cx_area_size - 1); + else + flash_cx_end_page = flash_cx_start_page; + } + + pr_info(" CX Start page: %d CX end page: %d Panel Start Page: %d" + "Panel End page: %d!\n", flash_cx_start_page, + flash_cx_end_page, flash_panel_start_page, + flash_panel_end_page); + + for (i = flash_cx_start_page; i <= flash_cx_end_page && keep_cx >= + SKIP_PANEL_CX_INIT; i++) { + pr_info("Skipping erase CX page %d!\n", i); + fromIDtoMask(i, mask, 4); + } + + + for (i = flash_panel_start_page; i <= flash_panel_end_page && keep_cx >= + SKIP_PANEL_INIT; i++) { + pr_info("Skipping erase Panel Init page %d!\n", i); + fromIDtoMask(i, mask, 4); + } + + for (i = 0; i < 4; i++) { + cmd2[5 + i] = cmd2[5 + i] & (~mask[i]); + index += scnprintf(buff + index, buff_len - index, + "%02X ", cmd2[5 + i]); + } + pr_info("Setting the page mask = %s\n", buff); + + pr_info("Writing page mask...\n"); + if (fts_write(cmd2, ARRAY_SIZE(cmd2)) < OK) { + pr_err("flash_erase_page_by_page: Page mask ERROR %08X\n", + ERROR_BUS_W); + return ERROR_BUS_W; + } + + if (fts_write(cmd1, ARRAY_SIZE(cmd1)) < OK) { + pr_err("flash_erase_page_by_page: Disable info ERROR %08X\n", + ERROR_BUS_W); + return ERROR_BUS_W; + } + + pr_info("Command erase pages sent ...\n"); + if (fts_write(cmd, ARRAY_SIZE(cmd)) < OK) { + pr_err("flash_erase_page_by_page: Erase ERROR %08X\n", + ERROR_BUS_W); + return ERROR_BUS_W; + } + + status = wait_for_flash_ready(FLASH_ERASE_CODE0); + + if (status != OK) { + pr_err("flash_erase_page_by_page: ERROR %08X\n", + ERROR_FLASH_NOT_READY); + return status | ERROR_FLASH_NOT_READY; + /* Flash not ready within the chosen time, better exit! */ + } + + pr_info("Erase flash page by page DONE!\n"); + + return OK; +} + +/** + * Start the DMA procedure which actually transfer and burn the data loaded + * from memory into the Flash + * @return OK if success or an error code which specify the type of error + */ +int start_flash_dma(void) +{ + int status; + u8 cmd[12] = { FLASH_CMD_WRITE_REGISTER, 0x20, 0x00, 0x00, + 0x6B, 0x00, 0x40, 0x42, 0x0F, 0x00, 0x00, + FLASH_DMA_CODE1 }; + + /* write the command to erase the flash */ + + pr_info("Command flash DMA ...\n"); + if (fts_write(cmd, ARRAY_SIZE(cmd)) < OK) { + pr_err("start_flash_dma: ERROR %08X\n", ERROR_BUS_W); + return ERROR_BUS_W; + } + + status = wait_for_flash_ready(FLASH_DMA_CODE0); + + if (status != OK) { + pr_err("start_flash_dma: ERROR %08X\n", ERROR_FLASH_NOT_READY); + return status | ERROR_FLASH_NOT_READY; + /* Flash not ready within the chosen time, better exit! */ + } + + pr_info("flash DMA DONE!\n"); + + return OK; +} + +/** + * Copy the FW data that should be burnt in the Flash into the memory and then + * the DMA will take care about burning it into the Flash + * @param address address in memory where to copy the data, possible values + * are FLASH_ADDR_CODE, FLASH_ADDR_CONFIG, FLASH_ADDR_CX + * @param data pointer to an array of byte which contain the data that should + * be copied into the memory + * @param size size of data + * @return OK if success or an error code which specify the type of error + */ +int fillFlash(u32 address, u8 *data, int size) +{ + int remaining = size, index = 0; + int toWrite = 0; + int byteBlock = 0; + int wheel = 0; + u32 addr = 0; + int res; + int delta; + u8 *buff = NULL; + + buff = kmalloc(max(DMA_CHUNK + 5, 12), GFP_KERNEL); + if (buff == NULL) { + pr_err("fillFlash: ERROR %08X\n", ERROR_ALLOC); + return ERROR_ALLOC; + } + + while (remaining > 0) { + byteBlock = 0; + addr = 0x00100000; + + while (byteBlock < FLASH_CHUNK && remaining > 0) { + index = 0; + if (remaining >= DMA_CHUNK) { + if ((byteBlock + DMA_CHUNK) <= FLASH_CHUNK) { + /* pr_err("fillFlash: 1\n"); */ + toWrite = DMA_CHUNK; + remaining -= DMA_CHUNK; + byteBlock += DMA_CHUNK; + } else { + /* pr_err("fillFlash: 2\n); */ + delta = FLASH_CHUNK - byteBlock; + toWrite = delta; + remaining -= delta; + byteBlock += delta; + } + } else { + if ((byteBlock + remaining) <= FLASH_CHUNK) { + /* pr_err("fillFlash: 3\n"); */ + toWrite = remaining; + byteBlock += remaining; + remaining = 0; + } else { + /* pr_err("fillFlash: 4\n"); */ + delta = FLASH_CHUNK - byteBlock; + toWrite = delta; + remaining -= delta; + byteBlock += delta; + } + } + + buff[index++] = FTS_CMD_HW_REG_W; + buff[index++] = (u8)((addr & 0xFF000000) >> 24); + buff[index++] = (u8)((addr & 0x00FF0000) >> 16); + buff[index++] = (u8)((addr & 0x0000FF00) >> 8); + buff[index++] = (u8)(addr & 0x000000FF); + + memcpy(&buff[index], data, toWrite); + /* pr_err("Command = %02X , address = %02X %02X + * , bytes = %d, data = %02X %02X, %02X %02X\n", + * buff[0], buff[1], buff[2], toWrite, buff[3], + * buff[4], buff[3 + toWrite-2], + * buff[3 + toWrite-1]); */ + if (fts_write_heap(buff, index + toWrite) < OK) { + pr_err("fillFlash: ERROR %08X\n", ERROR_BUS_W); + kfree(buff); + return ERROR_BUS_W; + } + + /* mdelay(5); */ + addr += toWrite; + data += toWrite; + } + + + /* configuring the DMA */ + byteBlock = byteBlock / 4 - 1; + index = 0; + + buff[index++] = FLASH_CMD_WRITE_REGISTER; + buff[index++] = 0x20; + buff[index++] = 0x00; + buff[index++] = 0x00; + buff[index++] = FLASH_DMA_CONFIG; + buff[index++] = 0x00; + buff[index++] = 0x00; + + addr = address + ((wheel * FLASH_CHUNK) / 4); + buff[index++] = (u8)((addr & 0x000000FF)); + buff[index++] = (u8)((addr & 0x0000FF00) >> 8); + buff[index++] = (u8)(byteBlock & 0x000000FF); + buff[index++] = (u8)((byteBlock & 0x0000FF00) >> 8); + buff[index++] = 0x00; + + pr_info("DMA Command = %02X , address = %02X %02X, words = %02X %02X\n", + buff[0], buff[8], buff[7], buff[10], buff[9]); + + if (fts_write_heap(buff, index) < OK) { + pr_err("Error during filling Flash! ERROR %08X\n", + ERROR_BUS_W); + kfree(buff); + return ERROR_BUS_W; + } + + res = start_flash_dma(); + if (res < OK) { + pr_err("Error during flashing DMA! ERROR %08X\n", res); + kfree(buff); + return res; + } + wheel++; + } + kfree(buff); + return OK; +} + + +/* + * Execute the procedure to burn a FW on FTM5/FTI IC + * + * @param fw - structure which contain the FW to be burnt + * @param force_burn - if >0, the flashing procedure will be forced and + * executed + * regardless the additional info, otherwise the FW in the file will be + * burned only if it is different from the one running in the IC + * @param keep_cx - if 1, the function preserves the CX/Panel Init area. + * Otherwise, it will be cleared. + * + * @return OK if success or an error code which specifies the type of error + * encountered + */ +int flash_burn(Firmware fw, int force_burn, int keep_cx) +{ + int res; + + if (!force_burn) { + /* Compare firmware, config, and CX versions */ + if (fw.fw_ver != (uint32_t)systemInfo.u16_fwVer || + fw.config_ver != (uint32_t)systemInfo.u16_cfgVer || + fw.cx_ver != (uint32_t)systemInfo.u16_cxVer) + goto start; + + for (res = EXTERNAL_RELEASE_INFO_SIZE - 1; res >= 0; res--) { + if (fw.externalRelease[res] != + systemInfo.u8_releaseInfo[res]) + goto start; + } + + pr_info("flash_burn: Firmware in the chip matches the firmware to flash! NO UPDATE ERROR %08X\n", + ERROR_FW_NO_UPDATE); + return ERROR_FW_NO_UPDATE | ERROR_FLASH_BURN_FAILED; + } else if (force_burn == CRC_CX && fw.sec2_size == 0) { + /* burn procedure to update the CX memory, if not present just + * skip it! + */ + for (res = EXTERNAL_RELEASE_INFO_SIZE - 1; res >= 0; res--) { + if (fw.externalRelease[res] != + systemInfo.u8_releaseInfo[res]) { + /* Avoid loading the CX because it is missing + * in the bin file, it just need to update + * to last fw+cfg because a new release */ + force_burn = 0; + goto start; + } + } + pr_info("flash_burn: CRC in CX but fw does not contain CX data! NO UPDATE ERROR %08X\n", + ERROR_FW_NO_UPDATE); + return ERROR_FW_NO_UPDATE | ERROR_FLASH_BURN_FAILED; + } + + /* Programming procedure start */ +start: + pr_info("Programming Procedure for flashing started:\n\n"); + + pr_info(" 1) SYSTEM RESET:\n"); + res = fts_system_reset(); + if (res < 0) { + pr_err(" system reset FAILED!\n"); + /* If there is no firmware, there is no controller ready event + * and there will be a timeout, we can keep going. But if + * there is an I2C error, we must exit. + */ + if (res != (ERROR_SYSTEM_RESET_FAIL | ERROR_TIMEOUT)) + return res | ERROR_FLASH_BURN_FAILED; + } else + pr_info(" system reset COMPLETED!\n\n"); + + msleep(100); /* required by HW for safe flash procedure */ + + pr_info(" 2) HOLD M3 :\n"); + + res = hold_m3(); + if (res < OK) { + pr_err(" hold_m3 FAILED!\n"); + return res | ERROR_FLASH_BURN_FAILED; + } + pr_info(" hold_m3 COMPLETED!\n\n"); + + pr_info("3) ENABLE UVLO AND AUTO POWER DOWN MODE :\n"); + res = flash_enable_uvlo_autopowerdown(); + if (res < OK) { + pr_err(" flash_enable_uvlo_autopowerdown FAILED!\n"); + return res | ERROR_FLASH_BURN_FAILED; + } + pr_err(" flash_enable_uvlo_autopowerdown COMPLETED!\n\n"); + + pr_info(" 4) FLASH UNLOCK:\n"); + res = flash_unlock(); + if (res < OK) { + pr_err(" flash unlock FAILED! ERROR %08X\n", + ERROR_FLASH_BURN_FAILED); + return res | ERROR_FLASH_BURN_FAILED; + } + pr_info(" flash unlock COMPLETED!\n\n"); + + pr_info(" 5) FLASH ERASE UNLOCK:\n"); + res = flash_erase_unlock(); + if (res < 0) { + pr_err(" flash unlock FAILED! ERROR %08X\n", + ERROR_FLASH_BURN_FAILED); + return res | ERROR_FLASH_BURN_FAILED; + } + pr_info(" flash unlock COMPLETED!\n\n"); + + pr_info(" 6) FLASH ERASE:\n"); + if (keep_cx > 0) { + if (fw.sec2_size != 0 && force_burn == CRC_CX) + res = flash_erase_page_by_page(SKIP_PANEL_INIT, &fw); + else + res = flash_erase_page_by_page(SKIP_PANEL_CX_INIT, &fw); + } else { + res = flash_erase_page_by_page(SKIP_PANEL_INIT, &fw); + if (fw.sec2_size == 0) + pr_err("WARNING!!! Erasing CX memory but no CX in fw file! touch will not work right after fw update!\n"); + } + + if (res < OK) { + pr_err(" flash erase FAILED! ERROR %08X\n", + ERROR_FLASH_BURN_FAILED); + return res | ERROR_FLASH_BURN_FAILED; + } + pr_info(" flash erase COMPLETED!\n\n"); + + pr_info(" 7) LOAD PROGRAM:\n"); + res = fillFlash(fw.code_start_addr, &fw.data[0], fw.sec0_size); + if (res < OK) { + pr_err(" load program ERROR %08X\n", + ERROR_FLASH_BURN_FAILED); + return res | ERROR_FLASH_BURN_FAILED; + } + pr_info(" load program DONE!\n"); + + pr_info(" 8) LOAD CONFIG:\n"); + res = fillFlash(fw.config_start_addr, &(fw.data[fw.sec0_size]), + fw.sec1_size); + if (res < OK) { + pr_err(" load config ERROR %08X\n", + ERROR_FLASH_BURN_FAILED); + return res | ERROR_FLASH_BURN_FAILED; + } + pr_info(" load config DONE!\n"); + + if (fw.sec2_size != 0 && (force_burn == CRC_CX || keep_cx <= 0)) { + pr_info(" 8.1) LOAD CX:\n"); + res = fillFlash(fw.cx_start_addr, + &(fw.data[fw.sec0_size + fw.sec1_size]), + fw.sec2_size); + if (res < OK) { + pr_err(" load cx ERROR %08X\n", + ERROR_FLASH_BURN_FAILED); + return res | ERROR_FLASH_BURN_FAILED; + } + pr_info(" load cx DONE!\n"); + } + + pr_info(" Flash burn COMPLETED!\n\n"); + + pr_info(" 9) SYSTEM RESET:\n"); + res = fts_system_reset(); + if (res < 0) { + pr_err(" system reset FAILED! ERROR %08X\n", + ERROR_FLASH_BURN_FAILED); + return res | ERROR_FLASH_BURN_FAILED; + } + pr_info(" system reset COMPLETED!\n\n"); + + pr_info(" 10) FINAL CHECK:\n"); + res = readSysInfo(0); + if (res < 0) { + pr_err("flash_burn: Unable to retrieve Chip INFO! ERROR %08X\n", + ERROR_FLASH_BURN_FAILED); + return res | ERROR_FLASH_BURN_FAILED; + } + + for (res = 0; res < EXTERNAL_RELEASE_INFO_SIZE; res++) { + if (fw.externalRelease[res] != systemInfo.u8_releaseInfo[res]) { + /* External release is printed during readSysInfo */ + pr_info(" Firmware in the chip different from the one that was burn!\n"); + return ERROR_FLASH_BURN_FAILED; + } + } + + pr_info(" Final check OK!\n"); + + return OK; +} diff --git a/fts_lib/ftsFlash.h b/fts_lib/ftsFlash.h new file mode 100644 index 0000000..2a52c53 --- /dev/null +++ b/fts_lib/ftsFlash.h @@ -0,0 +1,140 @@ +/* + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS API for Flashing the IC * + * * + ************************************************************************** + ************************************************************************** + * + */ + +/*! + * \file ftsFlash.h + * \brief Contains all the definitions and structs to handle the FW update + *process + */ + +#ifndef FTS_FLASH_H +#define FTS_FLASH_H + +#include "ftsSoftware.h" + +/* Flash possible status */ +#define FLASH_READY 0 /* /< value to indicate that the flash + * is ready */ +#define FLASH_BUSY 1 /* /< value to indicate that the flash + * is busy */ +#define FLASH_UNKNOWN -1 /* /< value to indicate an unknown + * status of the flash */ + +#define FLASH_STATUS_BYTES 1 /* /< number of bytes to check for read + * the flash status */ + + + +/* Flash timing parameters */ +#define FLASH_RETRY_COUNT 200 /* /< number of attempts to read the + * flash status */ +#define FLASH_WAIT_BEFORE_RETRY 50 /* /< time to wait in ms between status + * readings */ + + +#ifdef FW_H_FILE +#define PATH_FILE_FW "NULL" +#else +#define PATH_FILE_FW "ftm5_fw.ftb" /* new FW bin file name */ +#endif + +#ifdef ALIX +#define FLASH_CHUNK (32 * 1024) /* /< Max number of bytes that + * the + * DMA can burn on flash in + *one shot in FTI */ +#else +#define FLASH_CHUNK (64 * 1024) /* /< Max number of bytes that + * the + * DMA can burn on flash in + *one shot in FTI */ +#endif + +#define DMA_CHUNK 32 /* /< Max number of bytes that can be + * written in I2C to the DMA */ + +#define FLASH_ORG_INFO_INDEX 280 + +#define FLASH_PAGE_SIZE (4 * 1024) //page size of 4KB + +/** + * Define which kind of erase page by page should be performed + */ +typedef enum { + ERASE_ALL = 0, /* /< erase all the pages */ + SKIP_PANEL_INIT = 1, /* /< skip erase Panel Init Pages */ + SKIP_PANEL_CX_INIT = 2 /* /< skip erase Panel Init and CX Pages */ +} ErasePage; + +/** @addtogroup fw_file + * @{ + */ + +/** + * Struct which contains information and data of the FW that should be burnt + *into the IC + */ +typedef struct { + u8 *data; /* /< pointer to an array of bytes which represent the + * FW data */ + u16 fw_ver; /* /< FW version of the FW file */ + u16 config_ver; /* Config version of the FW file */ + u16 cx_ver; /* /< Cx version of the FW file */ + u8 externalRelease[EXTERNAL_RELEASE_INFO_SIZE]; /* /< External Release + * Info of the FW file + * */ + int data_size; /* /< dimension of data (the actual data to be burnt) */ + u32 sec0_size; /* /< dimension of section 0 (FW) in .ftb file */ + u32 sec1_size; /* /< dimension of section 1 (Config) in .ftb file */ + u32 sec2_size; /* /< dimension of section 2 (Cx) in .ftb file */ + u32 sec3_size; /* /< dimension of section 3 (TBD) in .ftb file */ + u8 fw_code_size; /* /< size of fw code in pages in + .ftb file */ + u8 panel_config_size;/* /< size of panel area in pages in + .ftb file */ + u8 cx_area_size;/* /< size of cx area in pages in + .ftb file */ + u8 fw_config_size;/* /< size of fw config in pages in + .ftb file */ + u32 code_start_addr; /* start addr for fw code */ + u32 cx_start_addr; /* start addr for cx area */ + u32 config_start_addr; /* start addr for config area */ +} Firmware; + +/** @}*/ + +/** @addtogroup flash_command + * @{ + */ + +int wait_for_flash_ready(u8 type); +int hold_m3(void); +int flash_erase_unlock(void); +int flash_full_erase(void); +int flash_erase_page_by_page(ErasePage keep_cx, Firmware *fw); +int start_flash_dma(void); +int fillFlash(u32 address, u8 *data, int size); + +int flash_unlock(void); +int getFWdata(const char *pathToFile, u8 **data, int *size); +int parseBinFile(u8 *fw_data, int fw_size, Firmware *fw, int keep_cx); +int readFwFile(const char *path, Firmware *fw, int keep_cx); +int flash_burn(Firmware fw, int force_burn, int keep_cx); +int flashProcedure(const char *path, int force, int keep_cx); +int flash_enable_uvlo_autopowerdown(void); + +#endif + +/** @}*/ diff --git a/fts_lib/ftsFrame.c b/fts_lib/ftsFrame.c new file mode 100644 index 0000000..b79f498 --- /dev/null +++ b/fts_lib/ftsFrame.c @@ -0,0 +1,667 @@ +/* + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS functions for getting frames * + * * + ************************************************************************** + ************************************************************************** + * + */ + +/*! + * \file ftsFrame.c + * \brief Contains all the functions to work with frames + */ + +#include "ftsCompensation.h" +#include "ftsCore.h" +#include "ftsError.h" +#include "ftsFrame.h" +#include "ftsHardware.h" +#include "ftsIO.h" +#include "ftsSoftware.h" +#include "ftsTool.h" +#include "ftsTime.h" + + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <stdarg.h> +#include <linux/serio.h> +#include <linux/time.h> +#include <linux/delay.h> +#include <linux/ctype.h> + + + + + +extern SysInfo systemInfo; /* /< forward declaration of the global variable + * of containing System Info Data */ + + +/** + * Read the channels lengths from the config memory + * @return OK if success or an error code which specify the type of error + */ +int getChannelsLength(void) +{ + int ret; + u8 data[2]; + + if (data == NULL) { + pr_err("getChannelsLength: ERROR %08X\n", ERROR_ALLOC); + return ERROR_ALLOC; + } + + ret = readConfig(ADDR_CONFIG_SENSE_LEN, data, 2); + if (ret < OK) { + pr_err("getChannelsLength: ERROR %08X\n", ret); + + return ret; + } + + systemInfo.u8_scrRxLen = (int)data[0]; + systemInfo.u8_scrTxLen = (int)data[1]; + + pr_info("Force_len = %d Sense_Len = %d\n", + systemInfo.u8_scrTxLen, systemInfo.u8_scrRxLen); + + return OK; +} + + + +/** + * Read and pack the frame data related to the nodes + * @param address address in memory when the frame data node start + * @param size amount of data to read + * @param frame pointer to an array of bytes which will contain the frame node + * data + * @return OK if success or an error code which specify the type of error + */ +int getFrameData(u16 address, int size, short *frame) +{ + int i, j, ret; + u8 *data = (u8 *)kmalloc(size * sizeof(u8), GFP_KERNEL); + + if (data == NULL) { + pr_err("getFrameData: ERROR %08X\n", ERROR_ALLOC); + return ERROR_ALLOC; + } + + ret = fts_writeReadU8UX(FTS_CMD_FRAMEBUFFER_R, BITS_16, address, data, + size, DUMMY_FRAMEBUFFER); + if (ret < OK) { + pr_err("getFrameData: ERROR %08X\n", ERROR_BUS_R); + kfree(data); + return ERROR_BUS_R; + } + j = 0; + for (i = 0; i < size; i += 2) { + frame[j] = (short)((data[i + 1] << 8) + data[i]); + j++; + } + kfree(data); + return OK; +} + + +/** + * Return the number of Sense Channels (Rx) + * @return number of Rx channels + */ +int getSenseLen(void) +{ + if (systemInfo.u8_scrRxLen == 0) + getChannelsLength(); + return systemInfo.u8_scrRxLen; +} + +/** + * Return the number of Force Channels (Tx) + * @return number of Tx channels + */ +int getForceLen(void) +{ + if (systemInfo.u8_scrTxLen == 0) + getChannelsLength(); + return systemInfo.u8_scrTxLen; +} + + +/******************** New API **************************/ + +/** + * Read a MS Frame from frame buffer memory + * @param type type of MS frame to read + * @param frame pointer to MutualSenseFrame variable which will contain the + * data + * @return > 0 if success specifying the number of node into the frame or + * an error code which specify the type of error + */ +int getMSFrame3(MSFrameType type, MutualSenseFrame *frame) +{ + u16 offset; + int ret, force_len, sense_len; + + force_len = getForceLen(); + sense_len = getSenseLen(); + + frame->node_data = NULL; + + pr_debug("%s: Starting to get frame %02X\n", __func__, + type); + switch (type) { + case MS_RAW: + offset = systemInfo.u16_msTchRawAddr; + goto LOAD_NORM; + case MS_FILTER: + offset = systemInfo.u16_msTchFilterAddr; + + goto LOAD_NORM; + case MS_STRENGTH: + offset = systemInfo.u16_msTchStrenAddr; + goto LOAD_NORM; + case MS_BASELINE: + offset = systemInfo.u16_msTchBaselineAddr; +LOAD_NORM: + if (force_len == 0 || sense_len == 0) { + pr_err("%s: number of channels not initialized ERROR %08X\n", + __func__, ERROR_CH_LEN); + return ERROR_CH_LEN | ERROR_GET_FRAME; + } + + break; + + case MS_KEY_RAW: + offset = systemInfo.u16_keyRawAddr; + goto LOAD_KEY; + case MS_KEY_FILTER: + offset = systemInfo.u16_keyFilterAddr; + goto LOAD_KEY; + case MS_KEY_STRENGTH: + offset = systemInfo.u16_keyStrenAddr; + goto LOAD_KEY; + case MS_KEY_BASELINE: + offset = systemInfo.u16_keyBaselineAddr; +LOAD_KEY: + if (systemInfo.u8_keyLen == 0) { + pr_err("%s: number of channels not initialized ERROR %08X\n", + __func__, ERROR_CH_LEN); + return ERROR_CH_LEN | ERROR_GET_FRAME; + } + force_len = 1; + sense_len = systemInfo.u8_keyLen; + break; + + case FRC_RAW: + offset = systemInfo.u16_frcRawAddr; + goto LOAD_FRC; + case FRC_FILTER: + offset = systemInfo.u16_frcFilterAddr; + goto LOAD_FRC; + case FRC_STRENGTH: + offset = systemInfo.u16_frcStrenAddr; + goto LOAD_FRC; + case FRC_BASELINE: + offset = systemInfo.u16_frcBaselineAddr; +LOAD_FRC: + if (force_len == 0) { + pr_err("%s: number of channels not initialized ERROR %08X\n", + __func__, ERROR_CH_LEN); + return ERROR_CH_LEN | ERROR_GET_FRAME; + } + sense_len = 1; + break; + default: + pr_err("%s: Invalid type ERROR %08X\n", __func__, + ERROR_OP_NOT_ALLOW | ERROR_GET_FRAME); + return ERROR_OP_NOT_ALLOW | ERROR_GET_FRAME; + } + + frame->node_data_size = ((force_len) * sense_len); + frame->header.force_node = force_len; + frame->header.sense_node = sense_len; + frame->header.type = type; + + pr_debug("%s: Force_len = %d Sense_len = %d Offset = %04X\n", + __func__, force_len, sense_len, offset); + + frame->node_data = (short *)kmalloc(frame->node_data_size * + sizeof(short), GFP_KERNEL); + if (frame->node_data == NULL) { + pr_err("%s: ERROR %08X\n", __func__, + ERROR_ALLOC | ERROR_GET_FRAME); + return ERROR_ALLOC | ERROR_GET_FRAME; + } + + ret = getFrameData(offset, frame->node_data_size * BYTES_PER_NODE, + (frame->node_data)); + if (ret < OK) { + pr_err("%s: ERROR %08X\n", __func__, ERROR_GET_FRAME_DATA); + kfree(frame->node_data); + frame->node_data = NULL; + return ret | ERROR_GET_FRAME_DATA | ERROR_GET_FRAME; + } + /* if you want to access one node i,j, + * compute the offset like: offset = i*columns + j = > frame[i, j] */ + + pr_debug("Frame acquired!\n"); + return frame->node_data_size; + /* return the number of data put inside frame */ +} + +/** + * Read a SS Frame from frame buffer + * @param type type of SS frame to read + * @param frame pointer to SelfSenseFrame variable which will contain the data + * @return > 0 if success specifying the number of node into frame or an + * error code which specify the type of error + */ +int getSSFrame3(SSFrameType type, SelfSenseFrame *frame) +{ + u16 offset_force, offset_sense; + int ret; + + frame->force_data = NULL; + frame->sense_data = NULL; + + frame->header.force_node = getForceLen(); /* use getForce/SenseLen + * because introduce + * a recover mechanism + * in case of len =0 */ + frame->header.sense_node = getSenseLen(); + + if (frame->header.force_node == 0 || frame->header.sense_node == 0) { + pr_err("%s: number of channels not initialized ERROR %08X\n", + __func__, ERROR_CH_LEN); + return ERROR_CH_LEN | ERROR_GET_FRAME; + } + + + pr_info("%s: Starting to get frame %02X\n", __func__, type); + switch (type) { + case SS_RAW: + offset_force = systemInfo.u16_ssTchTxRawAddr; + offset_sense = systemInfo.u16_ssTchRxRawAddr; + break; + case SS_FILTER: + offset_force = systemInfo.u16_ssTchTxFilterAddr; + offset_sense = systemInfo.u16_ssTchRxFilterAddr; + break; + case SS_STRENGTH: + offset_force = systemInfo.u16_ssTchTxStrenAddr; + offset_sense = systemInfo.u16_ssTchRxStrenAddr; + break; + case SS_BASELINE: + offset_force = systemInfo.u16_ssTchTxBaselineAddr; + offset_sense = systemInfo.u16_ssTchRxBaselineAddr; + break; + + case SS_HVR_RAW: + offset_force = systemInfo.u16_ssHvrTxRawAddr; + offset_sense = systemInfo.u16_ssHvrRxRawAddr; + break; + case SS_HVR_FILTER: + offset_force = systemInfo.u16_ssHvrTxFilterAddr; + offset_sense = systemInfo.u16_ssHvrRxFilterAddr; + break; + case SS_HVR_STRENGTH: + offset_force = systemInfo.u16_ssHvrTxStrenAddr; + offset_sense = systemInfo.u16_ssHvrRxStrenAddr; + break; + case SS_HVR_BASELINE: + offset_force = systemInfo.u16_ssHvrTxBaselineAddr; + offset_sense = systemInfo.u16_ssHvrRxBaselineAddr; + break; + + case SS_PRX_RAW: + offset_force = systemInfo.u16_ssPrxTxRawAddr; + offset_sense = systemInfo.u16_ssPrxRxRawAddr; + break; + case SS_PRX_FILTER: + offset_force = systemInfo.u16_ssPrxTxFilterAddr; + offset_sense = systemInfo.u16_ssPrxRxFilterAddr; + break; + case SS_PRX_STRENGTH: + offset_force = systemInfo.u16_ssPrxTxStrenAddr; + offset_sense = systemInfo.u16_ssPrxRxStrenAddr; + break; + case SS_PRX_BASELINE: + offset_force = systemInfo.u16_ssPrxTxBaselineAddr; + offset_sense = systemInfo.u16_ssPrxRxBaselineAddr; + break; + case SS_DETECT_RAW: + if (systemInfo.u8_ssDetScanSet == 0) { + offset_force = systemInfo.u16_ssDetRawAddr; + offset_sense = 0; + frame->header.sense_node = 0; + } else { + offset_sense = systemInfo.u16_ssDetRawAddr; + offset_force = 0; + frame->header.force_node = 0; + } + break; + + case SS_DETECT_FILTER: + if (systemInfo.u8_ssDetScanSet == 0) { + offset_force = systemInfo.u16_ssDetFilterAddr; + offset_sense = 0; + frame->header.sense_node = 0; + } else { + offset_sense = systemInfo.u16_ssDetFilterAddr; + offset_force = 0; + frame->header.force_node = 0; + } + break; + + case SS_DETECT_BASELINE: + if (systemInfo.u8_ssDetScanSet == 0) { + offset_force = systemInfo.u16_ssDetBaselineAddr; + offset_sense = 0; + frame->header.sense_node = 0; + } else { + offset_sense = systemInfo.u16_ssDetBaselineAddr; + offset_force = 0; + frame->header.force_node = 0; + } + break; + + case SS_DETECT_STRENGTH: + if (systemInfo.u8_ssDetScanSet == 0) { + offset_force = systemInfo.u16_ssDetStrenAddr; + offset_sense = 0; + frame->header.sense_node = 0; + } else { + offset_sense = systemInfo.u16_ssDetStrenAddr; + offset_force = 0; + frame->header.force_node = 0; + } + break; + + default: + pr_err("%s: Invalid type ERROR %08X\n", __func__, + ERROR_OP_NOT_ALLOW | ERROR_GET_FRAME); + return ERROR_OP_NOT_ALLOW | ERROR_GET_FRAME; + } + + frame->header.type = type; + + pr_info("%s: Force_len = %d Sense_len = %d Offset_force = %04X Offset_sense = %04X\n", + __func__, frame->header.force_node, + frame->header.sense_node, + offset_force, offset_sense); + + frame->force_data = (short *)kmalloc(frame->header.force_node * + sizeof(short), GFP_KERNEL); + if (frame->force_data == NULL) { + pr_err("%s: can not allocate force_data ERROR %08X\n", + __func__, ERROR_ALLOC | ERROR_GET_FRAME); + return ERROR_ALLOC | ERROR_GET_FRAME; + } + + frame->sense_data = (short *)kmalloc(frame->header.sense_node * + sizeof(short), GFP_KERNEL); + if (frame->sense_data == NULL) { + kfree(frame->force_data); + frame->force_data = NULL; + pr_err("%s: can not allocate sense_data ERROR %08X\n", + __func__, ERROR_ALLOC | ERROR_GET_FRAME); + return ERROR_ALLOC | ERROR_GET_FRAME; + } + + ret = getFrameData(offset_force, frame->header.force_node * + BYTES_PER_NODE, (frame->force_data)); + if (ret < OK) { + pr_err("%s: error while reading force data ERROR %08X\n", + __func__, ERROR_GET_FRAME_DATA); + kfree(frame->force_data); + frame->force_data = NULL; + kfree(frame->sense_data); + frame->sense_data = NULL; + return ret | ERROR_GET_FRAME_DATA | ERROR_GET_FRAME; + } + + ret = getFrameData(offset_sense, frame->header.sense_node * + BYTES_PER_NODE, (frame->sense_data)); + if (ret < OK) { + pr_err("%s: error while reading sense data ERROR %08X\n", + __func__, ERROR_GET_FRAME_DATA); + kfree(frame->force_data); + frame->force_data = NULL; + kfree(frame->sense_data); + frame->sense_data = NULL; + return ret | ERROR_GET_FRAME_DATA | ERROR_GET_FRAME; + } + /* if you want to access one node i,j, + * the offset like: offset = i*columns + j = > frame[i, j] */ + + pr_info("Frame acquired!\n"); + return frame->header.force_node + frame->header.sense_node; + /* return the number of data put inside frame */ +} + + +/** + * Read Initialization Data Header and check that the type loaded match with + * the one previously requested + * @param type type of Initialization data requested @link load_opt Load Host + * Data Option @endlink + * @param msHeader pointer to DataHeader variable which will contain the header + * info for the MS frame + * @param ssHeader pointer to DataHeader variable which will contain the header + * info for the SS frame + * @param address pointer to a variable which will contain the updated address + * to the next data + * @return OK if success or an error code which specify the type of error + */ +int readSyncDataHeader(u8 type, DataHeader *msHeader, DataHeader *ssHeader, + u64 *address) +{ + u64 offset = ADDR_FRAMEBUFFER; + u8 data[SYNCFRAME_DATA_HEADER]; + int ret; + + ret = fts_writeReadU8UX(FTS_CMD_FRAMEBUFFER_R, BITS_16, offset, data, + SYNCFRAME_DATA_HEADER, DUMMY_FRAMEBUFFER); + if (ret < OK) { /* i2c function have already a retry mechanism */ + pr_err("%s: error while reading data header ERROR %08X\n", + __func__, ret); + return ret; + } + + pr_info("Read Data Header done!\n"); + + if (data[0] != HEADER_SIGNATURE) { + pr_err("%s: The Header Signature was wrong! %02X != %02X ERROR %08X\n", + __func__, data[0], HEADER_SIGNATURE, + ERROR_WRONG_DATA_SIGN); + return ERROR_WRONG_DATA_SIGN; + } + + + if (data[1] != type) { + pr_err("%s: Wrong type found! %02X!=%02X ERROR %08X\n", + __func__, data[1], type, ERROR_DIFF_DATA_TYPE); + return ERROR_DIFF_DATA_TYPE; + } + + pr_info("Type = %02X of SyncFrame data OK!\n", type); + + msHeader->force_node = data[5]; + msHeader->sense_node = data[6]; + pr_info("MS Frame force_node = %d, sense_node = %d\n", + msHeader->force_node, msHeader->sense_node); + + ssHeader->force_node = data[7]; + ssHeader->sense_node = data[8]; + pr_info("SS Frame force_node = %d, sense_node = %d\n", + ssHeader->force_node, ssHeader->sense_node); + + *address = offset + SYNCFRAME_DATA_HEADER + data[4]; + + return OK; +} + +/** + * Read a Sync Frame from frame buffer which contain MS and SS data collected + * for the same scan + * @param type type of Sync frame to read, possible values: + * LOAD_SYNC_FRAME_RAW, LOAD_SYNC_FRAME_FILTER, LOAD_SYNC_FRAME_BASELINE, + * LOAD_SYNC_FRAME_STRENGTH + * @param msFrame pointer to MutualSenseFrame variable which will contain the + * MS data + * @param ssFrame pointer to SelfSenseFrame variable which will contain the SS + * data + * @return >0 if success specifying the total number of nodes copied into + * msFrame and ssFrame or an error code which specify the type of error + */ +int getSyncFrame(u8 type, MutualSenseFrame *msFrame, SelfSenseFrame *ssFrame) +{ + int res; + u64 address; + + msFrame->node_data = NULL; + ssFrame->force_data = NULL; + ssFrame->sense_data = NULL; + + pr_info("%s: Starting to get Sync Frame %02X...\n", __func__, type); + switch (type) { + case LOAD_SYNC_FRAME_RAW: + msFrame->header.type = MS_RAW; + ssFrame->header.type = SS_RAW; + break; + + case LOAD_SYNC_FRAME_FILTER: + msFrame->header.type = MS_FILTER; + ssFrame->header.type = SS_FILTER; + break; + + case LOAD_SYNC_FRAME_BASELINE: + msFrame->header.type = MS_BASELINE; + ssFrame->header.type = SS_BASELINE; + break; + + case LOAD_SYNC_FRAME_STRENGTH: + msFrame->header.type = MS_STRENGTH; + ssFrame->header.type = SS_STRENGTH; + break; + + default: + return ERROR_OP_NOT_ALLOW | ERROR_GET_FRAME; + } + + pr_info("%s: Requesting Sync Frame %02X...\n", __func__, type); + res = requestSyncFrame(type); + if (res < OK) { + pr_err("%s: error while requesting Sync Frame ERROR %08X\n", + __func__, res | ERROR_GET_FRAME_DATA); + return res | ERROR_GET_FRAME_DATA; + } + + res = readSyncDataHeader(type, &(msFrame->header), &(ssFrame->header), + &address); + if (res < OK) { + pr_err("%s: error while reading Sync Frame header... ERROR %08X\n", + __func__, res | ERROR_GET_FRAME_DATA); + return res | ERROR_GET_FRAME_DATA; + } + + msFrame->node_data_size = msFrame->header.force_node * + msFrame->header.sense_node; + + msFrame->node_data = (short *)kmalloc(msFrame->node_data_size * + sizeof(short), GFP_KERNEL); + if (msFrame->node_data == NULL) { + pr_err("%s: impossible allocate memory for MS frame... ERROR %08X\n", + __func__, ERROR_ALLOC | ERROR_GET_FRAME); + return ERROR_ALLOC | ERROR_GET_FRAME; + } + + pr_info("%s: Getting MS frame at %llx...\n", __func__, address); + res = getFrameData(address, (msFrame->node_data_size) * BYTES_PER_NODE, + (msFrame->node_data)); + if (res < OK) { + pr_err("%s: error while getting MS data...ERROR %08X\n", + __func__, res); + res |= ERROR_GET_FRAME_DATA | ERROR_GET_FRAME; + goto ERROR; + } + + /* move the offset */ + address += (msFrame->node_data_size) * BYTES_PER_NODE; + + ssFrame->force_data = (short *)kmalloc(ssFrame->header.force_node * + sizeof(short), GFP_KERNEL); + if (ssFrame->force_data == NULL) { + pr_err("%s: impossible allocate memory for SS force frame...ERROR %08X\n", + __func__, ERROR_ALLOC | ERROR_GET_FRAME); + res = ERROR_ALLOC | ERROR_GET_FRAME; + goto ERROR; + } + + pr_info("%s: Getting SS force frame at %llx...\n", __func__, address); + res = getFrameData(address, (ssFrame->header.force_node) * + BYTES_PER_NODE, (ssFrame->force_data)); + if (res < OK) { + pr_err("%s: error while getting SS force data...ERROR %08X\n", + __func__, res); + res |= ERROR_GET_FRAME_DATA | ERROR_GET_FRAME; + goto ERROR; + } + + /* move the offset */ + address += (ssFrame->header.force_node) * BYTES_PER_NODE; + + ssFrame->sense_data = (short *)kmalloc(ssFrame->header.sense_node * + sizeof(short), GFP_KERNEL); + if (ssFrame->sense_data == NULL) { + pr_err("%s: impossible allocate memory for SS sense frame...ERROR %08X\n", + __func__, ERROR_ALLOC | ERROR_GET_FRAME); + res = ERROR_ALLOC | ERROR_GET_FRAME; + goto ERROR; + } + + pr_info("%s: Getting SS sense frame at %llx...\n", __func__, address); + res = getFrameData(address, (ssFrame->header.sense_node) * + BYTES_PER_NODE, (ssFrame->sense_data)); + if (res < OK) { + pr_err("%s: error while getting SS sense data...ERROR %08X\n", + __func__, res); + res |= ERROR_GET_FRAME_DATA | ERROR_GET_FRAME; + goto ERROR; + } + +ERROR: + if (res < OK) { + if (msFrame->node_data != NULL) { + kfree(msFrame->node_data); + msFrame->node_data = NULL; + } + + if (ssFrame->force_data != NULL) { + kfree(ssFrame->force_data); + ssFrame->force_data = NULL; + } + + if (ssFrame->sense_data != NULL) { + kfree(ssFrame->sense_data); + ssFrame->sense_data = NULL; + } + pr_err("Getting Sync Frame FAILED! ERROR %08X!\n", res); + } else { + pr_info("Getting Sync Frame FINISHED!\n"); + res = msFrame->node_data_size + ssFrame->header.force_node + + ssFrame->header.sense_node; + } + return res; +} diff --git a/fts_lib/ftsFrame.h b/fts_lib/ftsFrame.h new file mode 100644 index 0000000..a2e516d --- /dev/null +++ b/fts_lib/ftsFrame.h @@ -0,0 +1,118 @@ +/* + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS functions for getting frames * + * * + ************************************************************************** + ************************************************************************** + * + */ + +/*! + * \file ftsFrame.h + * \brief Contains all the definitions and structs to work with frames + */ + + +#ifndef FTS_FRAME_H +#define FTS_FRAME_H + +#include "ftsSoftware.h" +#include "ftsCore.h" + + + +/* Number of data bytes for each node */ +#define BYTES_PER_NODE 2 /* /< number of data bytes for each node + * */ + + +#define RETRY_FRAME_DATA_READ 2 /* /< max number of attempts to read a + * frame */ +#define SYNCFRAME_DATA_HEADER (DATA_HEADER + 12) /* /< number of bytes of + * Sync Frame Header */ + +/** + * Possible types of MS frames + */ +typedef enum { + MS_RAW = 0, /* /< Mutual Sense Raw Frame */ + MS_FILTER = 1, /* /< Mutual Sense Filtered Frame */ + MS_STRENGTH = 2, /* /< Mutual Sense Strength Frame (Baseline-Raw) + * */ + MS_BASELINE = 3, /* /< Mutual Sense Baseline Frame */ + MS_KEY_RAW = 4, /* /< Mutual Sense Key Raw Frame */ + MS_KEY_FILTER = 5, /* /< Mutual Sense Key Filter Frame */ + MS_KEY_STRENGTH = 6, /* /< Mutual Sense Key Strength Frame + * (Baseline-Raw) */ + MS_KEY_BASELINE = 7, /* /< Mutual Sense Key Baseline Frame */ + FRC_RAW = 8, /* /< Force Raw Frame */ + FRC_FILTER = 9, /* /< Force Filtered Frame */ + FRC_STRENGTH = 10, /* /< Force Strength Frame (Baseline-Raw) */ + FRC_BASELINE = 11 /* /< Force Baseline Frame */ +} MSFrameType; + + +/** + * Possible types of SS frames + */ +typedef enum { + SS_RAW = 0, /* /< Self Sense Raw Frame */ + SS_FILTER = 1, /* /< Self Sense Filtered Frame */ + SS_STRENGTH = 2, /* /< Self Sense Strength Frame (Baseline-Raw) + * */ + SS_BASELINE = 3, /* /< Self Sense Baseline Frame */ + SS_HVR_RAW = 4, /* /< Self Sense Hover Raw Frame */ + SS_HVR_FILTER = 5, /* /< Self Sense Hover Filter Frame */ + SS_HVR_STRENGTH = 6, /* /< Self Sense Hover Strength Frame + * (Baseline-Raw) */ + SS_HVR_BASELINE = 7, /* /< Self Sense Hover Baseline Frame */ + SS_PRX_RAW = 8, /* /< Self Sense Proximity Raw Frame */ + SS_PRX_FILTER = 9, /* /< Self Sense Proximity Filtered Frame */ + SS_PRX_STRENGTH = 10, /* /< Self Sense Proximity Strength Frame + * (Baseline-Raw) */ + SS_PRX_BASELINE = 11, /* /< Self Sense Proximity Baseline Frame */ + + SS_DETECT_RAW = 12, /* /< Self Sense Detect Raw Frame */ + SS_DETECT_FILTER = 13, /* /< Self Sense Detect Filter Frame */ + SS_DETECT_STRENGTH = 14, /* /< Self Sense Detect Strength Frame */ + SS_DETECT_BASELINE = 15 /* /< Self Sense Detect Baseline Frame */ +} SSFrameType; + + +/** + * Struct which contains the data of a MS Frame + */ +typedef struct { + DataHeader header; /* /< Header which contain basic info of the + * frame */ + short *node_data; /* /< Data of the frame */ + int node_data_size; /* /< Dimension of the data of the frame */ +} MutualSenseFrame; + +/** + * Struct which contains the data of a SS Frame + */ +typedef struct { + DataHeader header; /* /< Header which contain basic info of the + * frame */ + short *force_data; /* /< Force Channels Data */ + short *sense_data; /* /< Sense Channels Data */ +} SelfSenseFrame; + + +int getChannelsLength(void); +int getFrameData(u16 address, int size, short *frame); +int getSenseLen(void); +int getForceLen(void); +int getMSFrame3(MSFrameType type, MutualSenseFrame *frame); +int getSSFrame3(SSFrameType type, SelfSenseFrame *frame); +int readSyncDataHeader(u8 type, DataHeader *msHeader, DataHeader *ssHeader, + u64 *address); +int getSyncFrame(u8 type, MutualSenseFrame *msFrame, SelfSenseFrame *ssFrame); +#endif diff --git a/fts_lib/ftsGesture.c b/fts_lib/ftsGesture.c new file mode 100644 index 0000000..2810d42 --- /dev/null +++ b/fts_lib/ftsGesture.c @@ -0,0 +1,377 @@ +/* + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS Gesture Utilities ** + * * + ************************************************************************** + ************************************************************************** + * + */ + +/*! + * \file ftsGesture.c + * \brief Contains all the functions and variable to handle the Gesture + * Detection features + */ + +#include "ftsSoftware.h" +#include "ftsCore.h" +#include "ftsError.h" +#include "ftsGesture.h" +#include "ftsIO.h" +#include "ftsTime.h" +#include "ftsTool.h" + +/* /< store the gesture bitmask which the host want to enable. + * If bit set 1 the corresponding gesture will be detected in Gesture Mode */ +static u8 gesture_mask[GESTURE_MASK_SIZE] = { 0 }; +/* /< store the x coordinates of the points draw by the user + * when a gesture is detected */ +u16 gesture_coordinates_x[GESTURE_MAX_COORDS_PAIRS_REPORT] = { 0 }; +/* /< store the y coordinates of the points draw by the user + * when a gesture is detected */ +u16 gesture_coordinates_y[GESTURE_MAX_COORDS_PAIRS_REPORT] = { 0 }; +/* /< number of coordinates pairs (points) reported with the detected gesture */ +int gesture_coords_reported = ERROR_OP_NOT_ALLOW; +static u8 refreshGestureMask; /* /< flag which indicate if there is + * the need to set the gesture mask in the FW */ +struct mutex gestureMask_mutex; /* /< mutex used to control access on gesture + * shared variables */ + +/** + * Update the gesture mask stored in the driver and have to be used in gesture + * mode + * @param mask pointer to a byte array which store the gesture mask update + * that want to be performed. + * @param size dimension in byte of mask. This size can be <= + * GESTURE_MASK_SIZE. + * If size < GESTURE_MASK_SIZE the bytes of mask are considering continuos + * and starting from the less significant byte. + * @param en 0 = enable the gestures set in mask, 1 = disable the gestures set + * in mask + * @return OK if success or an error code which specify the type of error + */ +int updateGestureMask(u8 *mask, int size, int en) +{ + u8 temp; + int i; + + if (mask != NULL) { + if (size <= GESTURE_MASK_SIZE) { + if (en == FEAT_ENABLE) { + mutex_lock(&gestureMask_mutex); + pr_info("updateGestureMask: setting gesture mask to enable...\n"); + if (mask != NULL) + for (i = 0; i < size; i++) + gesture_mask[i] = + gesture_mask[i] | + mask[i]; + /* back up of the gesture enabled */ + refreshGestureMask = 1; + pr_info("updateGestureMask: gesture mask to enable SET!\n"); + mutex_unlock(&gestureMask_mutex); + return OK; + } else if (en == FEAT_DISABLE) { + mutex_lock(&gestureMask_mutex); + pr_info("updateGestureMask: setting gesture mask to disable...\n"); + for (i = 0; i < size; i++) { + temp = gesture_mask[i] ^ mask[i]; + /* enabled XOR disabled */ + gesture_mask[i] = temp & + gesture_mask[i]; + /* temp AND enabled + * disable the gestures that were + * enabled */ + } + pr_info("updateGestureMask: gesture mask to disable SET!\n"); + refreshGestureMask = 1; + mutex_unlock(&gestureMask_mutex); + return OK; + } else { + pr_err("%s: Enable parameter Invalid! %d != %d or %d ERROR %08X\n", + __func__, + en, + FEAT_DISABLE, FEAT_ENABLE, + ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + } else { + pr_err("updateGestureMask: Size not valid! %d > %d ERROR %08X\n", + size, GESTURE_MASK_SIZE, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + } else { + pr_err("updateGestureMask: Mask NULL! ERROR %08X\n", + ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } +} + +/** + * Enable in the FW the gesture mask to be used in gesture mode + * @param mask pointer to a byte array which store the gesture mask update + * that want to be sent to the FW, if NULL, will be used gesture_mask + * set previously without any changes. + * @param size dimension in byte of mask. This size can be <= + * GESTURE_MASK_SIZE. + * If size < GESTURE_MASK_SIZE the bytes of mask are considering continuos and + * starting from the less significant byte. + * @return OK if success or an error code which specify the type of error + */ +int enableGesture(u8 *mask, int size) +{ + int i, res; + + pr_info("Trying to enable gesture...\n"); + + if (size <= GESTURE_MASK_SIZE) { + mutex_lock(&gestureMask_mutex); + if (mask != NULL) + for (i = 0; i < size; i++) + gesture_mask[i] = gesture_mask[i] | mask[i]; + /* back up of the gesture enabled */ + + res = setFeatures(FEAT_SEL_GESTURE, gesture_mask, + GESTURE_MASK_SIZE); + if (res < OK) { + pr_err("enableGesture: ERROR %08X\n", res); + goto END; + } + + pr_info("enableGesture DONE!\n"); + res = OK; + +END: + mutex_unlock(&gestureMask_mutex); + return res; + } else { + pr_err("enableGesture: Size not valid! %d > %d ERROR %08X\n", + size, GESTURE_MASK_SIZE, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } +} + +/** + * Disable in the FW the gesture mask to be used in gesture mode + * @param mask pointer to a byte array which store the gesture mask update that + * want to be sent to the FW, if NULL, all the gestures will be disabled. + * @param size dimension in byte of mask. This size can be <= + * GESTURE_MASK_SIZE. + * If size < GESTURE_MASK_SIZE the bytes of mask are considering continuos and + * starting from the less significant byte. + * @return OK if success or an error code which specify the type of error + */ +int disableGesture(u8 *mask, int size) +{ + u8 temp; + int i, res; + u8 *pointer; + + + pr_info("Trying to disable gesture...\n"); + + + if (size <= GESTURE_MASK_SIZE) { + mutex_lock(&gestureMask_mutex); + if (mask != NULL) { + for (i = 0; i < size; i++) { + temp = gesture_mask[i] ^ mask[i]; + /* enabled mask XOR disabled mask */ + gesture_mask[i] = temp & gesture_mask[i]; + /* temp AND enabled + * disable the gestures that are specified and + * previously enabled */ + } + + pointer = gesture_mask; + } else { + i = 0; /* if NULL is passed disable all the possible + * gestures */ + pointer = (u8 *)&i; + } + + res = setFeatures(FEAT_SEL_GESTURE, pointer, GESTURE_MASK_SIZE); + if (res < OK) { + pr_err("disableGesture: ERROR %08X\n", res); + goto END; + } + + pr_info("disableGesture DONE!\n"); + + res = OK; + +END: + mutex_unlock(&gestureMask_mutex); + return res; + } else { + pr_err("disableGesture: Size not valid! %d > %d ERROR %08X\n", + size, GESTURE_MASK_SIZE, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } +} + +/** + * Perform all the steps required to put the chip in gesture mode + * @param reload if set to 1, before entering in gesture mode it will re-enable + * in the FW the last defined gesture mask + * @return OK if success or an error code which specify the type of error + */ +int enterGestureMode(int reload) +{ + int res, ret; + + res = fts_enableInterrupt(false); + if (res < OK) { + pr_err("enterGestureMode: ERROR %08X\n", + res | ERROR_DISABLE_INTER); + return res | ERROR_DISABLE_INTER; + } + + if (reload == 1 || refreshGestureMask == 1) { + res = enableGesture(NULL, 0); + if (res < OK) { + pr_err("enterGestureMode: enableGesture ERROR %08X\n", + res); + goto END; + } + + refreshGestureMask = 0; + } + + res = setScanMode(SCAN_MODE_LOW_POWER, 0); + if (res < OK) { + pr_err("enterGestureMode: enter gesture mode ERROR %08X\n", + res); + goto END; + } + + res = OK; +END: + ret = fts_enableInterrupt(true); + if (ret < OK) { + pr_err("enterGestureMode: fts_enableInterrupt ERROR %08X\n", + res | ERROR_ENABLE_INTER); + res |= ret | ERROR_ENABLE_INTER; + } + + + return res; +} + +/** + * Check if one or more Gesture IDs are currently enabled in gesture_mask + * @return FEAT_ENABLE if one or more gesture ids are enabled, FEAT_DISABLE if + * all the gesture ids are currently disabled + */ +int isAnyGestureActive(void) +{ + int res = 0; + + while (res < (GESTURE_MASK_SIZE - 1) && gesture_mask[res] == 0) + /* -1 because in any case the last gesture mask byte will + * be evaluated with the following if */ + res++; + + if (gesture_mask[res] != 0) { + pr_info("%s: Active Gestures Found! gesture_mask[%d] = %02X !\n", + __func__, res, gesture_mask[res]); + return FEAT_ENABLE; + } else { + pr_info("%s: All Gestures Disabled!\n", __func__); + return FEAT_DISABLE; + } +} + + +/** + * Read from the frame buffer the gesture coordinates pairs of the points draw + * by an user when a gesture is detected + * @param event pointer to a byte array which contains the gesture event + * reported + * by the fw when a gesture is detected + * @return OK if success or an error code which specify the type of error + */ +int readGestureCoords(u8 *event) +{ + int i = 0; + u64 address = 0; + int res; + + u8 val[GESTURE_MAX_COORDS_PAIRS_REPORT * 4]; + + /* the max coordinates to read are GESTURE_COORDS_REPORT_MAX*4 + * (because each coordinate is a short(*2) and we have x and y) */ + + + if (event[0] == EVT_ID_USER_REPORT && + event[1] == EVT_TYPE_USER_GESTURE) { + address = (event[4] << 8) | event[3]; /* Offset in framebuff */ + gesture_coords_reported = event[5]; /* number of pairs + * coords reported */ + if (gesture_coords_reported > GESTURE_MAX_COORDS_PAIRS_REPORT) { + pr_err("%s: FW reported more than %d points for the gestures! Decreasing to %d\n", + __func__, gesture_coords_reported, + GESTURE_MAX_COORDS_PAIRS_REPORT); + gesture_coords_reported = + GESTURE_MAX_COORDS_PAIRS_REPORT; + } + + pr_info("%s: Offset: %llx , coords pairs = %d\n", + __func__, address, gesture_coords_reported); + + res = fts_writeReadU8UX(FTS_CMD_FRAMEBUFFER_R, BITS_16, address, + val, (gesture_coords_reported * 2 * 2), + DUMMY_FRAMEBUFFER); + /* *2 because each coord is made by 2 bytes, + * *2 because there are x and y */ + if (res < OK) { + pr_err("%s: Cannot read the coordinates! ERROR %08X\n", + __func__, res); + gesture_coords_reported = ERROR_OP_NOT_ALLOW; + return res; + } + + /* all the points of the gesture are stored in val */ + for (i = 0; i < gesture_coords_reported; i++) { + gesture_coordinates_x[i] = + (((u16)val[i * 2 + 1]) & 0x0F) << 8 | + (((u16)val[i * 2]) & 0xFF); + gesture_coordinates_y[i] = + (((u16)val[gesture_coords_reported * + 2 + i * 2 + 1]) & 0x0F) << 8 | + (((u16)val[gesture_coords_reported * + 2 + i * 2]) & 0xFF); + } + + + pr_info("%s: Reading Gesture Coordinates DONE!\n", __func__); + return OK; + } else { + pr_err("%s: The event passsed as argument is invalid! ERROR %08X\n", + __func__, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } +} + +/** + * Return the coordinates of the points stored during the last detected gesture + * @param x output parameter which will store the address of the array + * containing the x coordinates + * @param y output parameter which will store the address of the array + * containing the y coordinates + * @return the number of points (x,y) stored and therefore the size of the x + * and y array returned. + */ +int getGestureCoords(u16 **x, u16 **y) +{ + *x = gesture_coordinates_x; + *y = gesture_coordinates_y; + pr_info("%s: Number of gesture coordinates pairs returned = %d\n", + __func__, gesture_coords_reported); + return gesture_coords_reported; +} diff --git a/fts_lib/ftsGesture.h b/fts_lib/ftsGesture.h new file mode 100644 index 0000000..cb59737 --- /dev/null +++ b/fts_lib/ftsGesture.h @@ -0,0 +1,47 @@ +/* + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS Gesture Utilities * + * * + ************************************************************************** + ************************************************************************** + * + */ + +/*! + * \file ftsGesture.h + * \brief Contains all the macro and prototypes to handle the Gesture Detection + * features + */ + + +#ifndef FTS_GESTURE_H_ +#define FTS_GESTURE_H_ + + + + +#include "ftsHardware.h" + +#define GESTURE_MASK_SIZE 4 /* /< number of bytes of the + * gesture mask */ + +#define GESTURE_MAX_COORDS_PAIRS_REPORT 100 /* /< max number of gestures + * coordinates pairs reported */ + + + +int updateGestureMask(u8 *mask, int size, int en); +int disableGesture(u8 *mask, int size); +int enableGesture(u8 *mask, int size); +int enterGestureMode(int reload); +int isAnyGestureActive(void); +int readGestureCoords(u8 *event); +int getGestureCoords(u16 **x, u16 **y); + +#endif /* ! _GESTURE_H_ */ diff --git a/fts_lib/ftsHardware.h b/fts_lib/ftsHardware.h new file mode 100644 index 0000000..20844cb --- /dev/null +++ b/fts_lib/ftsHardware.h @@ -0,0 +1,319 @@ +/* + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * HW related data * + * * + ************************************************************************** + ************************************************************************** + * + */ + +/*! + * \file ftsHardware.h + * \brief Contains all the definitions and information related to the IC + * from an hardware point of view + */ + +#ifndef FTS_HARDWARE_H +#define FTS_HARDWARE_H + +/* DIGITAL CHIP INFO */ +#define ALIX + +#ifdef ALIX +#define DCHIP_ID_0 0x36 /* /< LSB chip ID for FTM5 */ +#define DCHIP_ID_1 0x48 /* /< MSB chip ID for FTM5 */ +#else +#define DCHIP_ID_0 0x36 /* /< LSB chip ID for FTM5 */ +#define DCHIP_ID_1 0x39 /* /< MSB chip ID for FTM5 */ +#endif + +#define DCHIP_FW_VER_BYTE 2 /* /< number of bytes of the fw + * versions */ + +/* CHUNKS */ +#define READ_CHUNK 1024 /* /< chunk dimension of + * a single i2c read, + * max allowed value is 2kB */ +#define WRITE_CHUNK 1024 /* /< chunk dimension of + * a single i2c write, + * max allowed value is 2kB */ +#define MEMORY_CHUNK 1024 /* /< chunk dimenasion of + * a single i2c write on mem, + * max allowed value is 2kB */ + +#define READ_FILTERED_RAW /* /< To understand + * which type of RAW data to read + * OCTA panel needs to read Filtered RAW */ + +/* PROTOCOL INFO */ +#define SPI_INETERFACE +#ifdef I2C_INTERFACE +#define I2C_SAD 0x49 /* /< slave address of the IC */ +#else +#define SPI_DELAY_CS 10 /* /< time in usec to wait + * before rising the CS */ +#endif + +#define IER_ENABLE 0x41 /* /< value to write in IER_ADDR + * to enable the interrupts */ +#define IER_DISABLE 0x00 /* /< value to write in IER_ADDR + * to disable the interrupts + **/ + +/* FLASH COMMAND */ +/** @defgroup flash_command Flash Commands + * All the commands that works with the flash of the IC + * @{ + */ +#define FLASH_CMD_UNLOCK 0xF7 + +#define FLASH_CMD_READ_REGISTER 0xFA +#define FLASH_CMD_WRITE_REGISTER 0xFA + +/* FLASH UNLOCK PARAMETER */ +#define FLASH_UNLOCK_CODE0 0x25 +#define FLASH_UNLOCK_CODE1 0x20 +#define FLASH_UNLOCK_CODE2 0x6B +#define FLASH_UNLOCK_CODE3 0x00 + +/* FLASH UVLO ENABLE PARAMETER */ +#define FLASH_UVLO_ENABLE_CODE0 0x1B +#define FLASH_UVLO_ENABLE_CODE1 0x66 + +/* FLASH AUTOPOWERDOWN ENABLE PARAMETER */ +#define FLASH_AUTOPOWERDOWN_ENABLE_CODE0 0x68 +#define FLASH_AUTOPOWERDOWN_ENABLE_CODE1 0x13 + +#define INFO_BLOCK_SIZE 16384 +#define INFO_BLOCK_LOCKDOWN 0x1800 +#define INFO_BLOCK_IOFF 0x19E8 +#define INFO_BLOCK_OSC 0x1EAC +#define INFO_BLOCK_AOFFSET 0x1D48 +#define INFO_BLOCK_CG 0x1E08 + +/* FLASH ERASE and DMA PARAMETER */ +#define FLASH_ERASE_START 0x80 +#define FLASH_ERASE_CODE1 0xC0 +#define FLASH_DMA_CODE1 0xC0 +#define FLASH_ERASE_UNLOCK_CODE0 0xDE +#define FLASH_ERASE_UNLOCK_CODE1 0x03 +#define FLASH_ERASE_CODE0 0x6A +#define FLASH_DMA_CODE0 0x71 +#define FLASH_DMA_CONFIG 0x72 +#define FLASH_NUM_PAGE 32 /* /< number of pages in main + * flash */ +#define FLASH_CX_PAGE_START 28 /* /< starting page which + * contain Cx data */ +#define FLASH_CX_PAGE_END 30 /* /< last page which contain Cx + * data */ +#define FLASH_PANEL_PAGE_START 26 /* /< starting page which + * contain Panel Init data */ +#define FLASH_PANEL_PAGE_END 27 /* /< last page which contain + * Panel Init data */ +/** @} */ + +/* FLASH ADDRESS */ +#define FLASH_ADDR_CODE 0x00000000 /* /< address of code in + * FTI */ +#define FLASH_ADDR_CONFIG 0x00007C00 /* /< address of config + * in FTI */ +#define FLASH_ADDR_CX 0x00007000 /* /< address of Init + * data in FTI */ + +/* SIZES FW, CODE, CONFIG, MEMH */ +/** @defgroup fw_file FW file info + * All the info related to the fw file + * @{ + */ +#define FW_HEADER_SIZE 64 /* /< dimension of the header in + * .fts file */ +#define FW_HEADER_SIGNATURE 0xAA55AA55 /* /< header signature + * */ +#define FW_FTB_VER 0x00000001 /* /< .ftb version */ +#define FW_BYTES_ALIGN 4 /* /< allignment of the info */ +#define FW_BIN_VER_OFFSET 16 /* /< offset of the fw version + * in the .ftb file */ +#define FW_BIN_CONFIG_ID_OFFSET 20 /* /< offset of the config id in + * the .ftb file */ +#define FW_CX_VERSION (16 + 4)/* CX version offset in sec2 of + * FW */ +/** @} */ + +/* FIFO */ +#define FIFO_EVENT_SIZE 8 /* /< number of bytes of one + * event */ +#define FIFO_DEPTH 32 /* /< max number of events that + * the FIFO can collect before + * going in overflow in FTM5 */ + +#ifdef I2C_INTERFACE +#define FIFO_CMD_READALL 0x86 /* /< command to read all + * the events in the FIFO */ +#else +#define FIFO_CMD_READALL 0x87 /* /< command to read all + * the events in the FIFO */ +#endif +#define FIFO_CMD_READONE FIFO_CMD_READALL/* /< commad to read + * one event from FIFO + **/ + + +/* OP CODES FOR MEMORY (based on protocol) */ +#ifdef I2C_INTERFACE +#define FTS_CMD_HW_REG_R 0xFA /* /< command to read an hw register if + * FTI */ +#define FTS_CMD_HW_REG_W 0xFA /* /< command to write an hw register if + * FTI */ +#define FTS_CMD_FRAMEBUFFER_W 0xA6 /* /< command to write the framebuffer + * if FTI + */ +#define FTS_CMD_FRAMEBUFFER_R 0xA6 /* /< command to read the framebuffer if + * FTI */ +#define FTS_CMD_CONFIG_R 0xA8 /* /< command to read the config memory + * if FTI */ +#define FTS_CMD_CONFIG_W 0xA8 /* /< command to write the config memory + * if FTI */ +#else +#define FTS_CMD_HW_REG_R 0xFB /* /< command to read an hw register if + * FTI */ +#define FTS_CMD_HW_REG_W 0xFA /* /< command to write an hw register if + * FTI */ +#define FTS_CMD_FRAMEBUFFER_W 0xA6 /* /< command to write the framebuffer + * if FTI + */ +#define FTS_CMD_FRAMEBUFFER_R 0xA7 /* /< command to read the framebuffer if + * FTI */ +#define FTS_CMD_CONFIG_R 0xA9 /* /< command to read the config memory + * if FTI */ +#define FTS_CMD_CONFIG_W 0xA8 /* /< command to write the config memory + * if FTI */ +#endif + +/* DUMMY BYTES DATA */ +#ifndef I2C_INTERFACE +#define DUMMY_HW_REG 1 /* /< 1 if the first byte read from HW + * register is dummy */ +#define DUMMY_FRAMEBUFFER 1 /* /< 1 if the first byte read from + * Frame buffer is dummy */ +#define DUMMY_CONFIG 1 /* /< 1 if the first byte read from + * Config Memory is dummy */ +#define DUMMY_FIFO 1 /* /< 1 if the first byte read from FIFO + * is dummy */ +#else +#define DUMMY_HW_REG 0 /* /< 1 if the first byte read from HW + * register is dummy */ +#define DUMMY_FRAMEBUFFER 0 /* /< 1 if the first byte read from + * Frame buffer is dummy */ +#define DUMMY_CONFIG 0 /* /< 1 if the first byte read from + * Config Memory is dummy */ +#define DUMMY_FIFO 0 /* /< 1 if the first byte read from FIFO + * is dummy */ +#endif + +/** @defgroup hw_adr HW Address + * @ingroup address + * Important addresses of hardware registers (and sometimes their important + * values) + * @{ + */ + +/* IMPORTANT HW ADDRESSES (u64) */ +#define ADDR_FRAMEBUFFER ((u64)0x0000000000000000) /* /< frame + * buffer + * address in + * memory */ +#define ADDR_ERROR_DUMP ((u64)0x000000000000EF80) /* /< start + * address dump + * error log */ + +/* SYSTEM RESET INFO */ +#define ADDR_SYSTEM_RESET ((u64)0x0000000020000024) /* /< address of + * System + * control + * register in + * FTI */ +/* value to write in SYSTEM_RESET_ADDRESS to perform a system reset in FTM5 */ +#define SYSTEM_RESET_VALUE 0x81 + + +/* REMAP REGISTER */ +#define ADDR_BOOT_OPTION ((u64)0x0000000020000025) /* /< address of + * Boot option + * register */ + +/* INTERRUPT INFO */ +#define ADDR_IER ((u64)0x0000000020000029) /* /< address of + * the Interrupt + * enable + * register in + * FTMI */ + +/* Chip ID/Fw Version */ +#define ADDR_DCHIP_ID ((u64)0x0000000020000000) /* /< chip id + * address for + * FTI */ +#define ADDR_DCHIP_FW_VER ((u64)0x0000000020000004) /* /< fw version + * address for + * FTI */ + +/* INTERFACE REGISTER */ +#define ADDR_ICR ((u64)0x000000002000002D) /* /< address of + * Device + * control + * register + * to set the + * communication + * protocol + * (SPI/I2C) */ + +#define SPI4_MASK 0x02 /* /< bit to set spi4 */ + +#define ADDR_FLASH_STATUS ((u64)0x0000000020000068) +#define ADDR_INFOBLOCK ((u64)0x0000000000040000) + + +/* CRC ADDR */ +#define ADDR_CRC ((u64)0x0000000020000078) /* /< address of + * the CRC + * control + * register in + * FTI */ +#define CRC_MASK 0x03 /* /< bitmask which reveal if + * there is a CRC error in the flash */ + +#define ADDR_CONFIG_OFFSET ((u64)0x0000000000000000) /* /< config + * address in + * memory if FTI + * */ + +#define ADDR_GPIO_INPUT ((u64)0x0000000020000030) /* /< address of + * GPIO input + * register */ +#define ADDR_GPIO_DIRECTION ((u64)0x0000000020000032) /* /< address of + * GPIO + * direction + * register */ +#define ADDR_GPIO_PULLUP ((u64)0x0000000020000034) /* /< address of + * GPIO pullup + * register */ +#define ADDR_GPIO_CONFIG_REG0 ((u64)0x000000002000003D) /* /< address of + * GPIO config + * register */ +#define ADDR_GPIO_CONFIG_REG2 ((u64)0x000000002000003F) /* /< address of + * GPIO config + * register */ + +#define ADDR_GPIO_CONFIG_REG3 ((u64)0x000000002000003E) /* /< address of + * GPIO config + * register */ + +/**@}*/ + + +#endif diff --git a/fts_lib/ftsIO.c b/fts_lib/ftsIO.c new file mode 100644 index 0000000..9443fdd --- /dev/null +++ b/fts_lib/ftsIO.c @@ -0,0 +1,924 @@ +/* + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * I2C/SPI Communication * + * * + ************************************************************************** + ************************************************************************** + * + */ + +/*! + * \file ftsIO.c + * \brief Contains all the functions which handle with the I2C/SPI + *communication + */ + + +#include "ftsSoftware.h" + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <stdarg.h> +#include <linux/delay.h> +#include <linux/ctype.h> +#include <linux/of_gpio.h> + +#ifdef I2C_INTERFACE +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +static u16 I2CSAD; /* /< slave address of the IC in the i2c bus */ +#else +#include <linux/spi/spidev.h> +#endif + +static void *client; /* /< bus client retrived by the OS and + * used to execute the bus transfers */ + + + +#include "ftsCore.h" +#include "ftsError.h" +#include "ftsHardware.h" +#include "ftsIO.h" + + +/** + * Initialize the static client variable of the fts_lib library in order + * to allow any i2c/spi transaction in the driver (Must be called in the probe) + * @param clt pointer to i2c_client or spi_device struct which identify the bus + * slave device + * @return OK + */ +int openChannel(void *clt) +{ + client = clt; +#ifdef I2C_INTERFACE + I2CSAD = ((struct i2c_client *)clt)->addr; + pr_info("openChannel: SAD: %02X\n", I2CSAD); +#else + pr_info("%s: spi_master: flags = %04X !\n", __func__, + ((struct spi_device *)client)->master->flags); + pr_info("%s: spi_device: max_speed = %d chip select = %02X bits_per_words = %d mode = %04X !\n", + __func__, ((struct spi_device *)client)->max_speed_hz, + ((struct spi_device *)client)->chip_select, + ((struct spi_device *)client)->bits_per_word, + ((struct spi_device *)client)->mode); + pr_info("openChannel: completed!\n"); +#endif + return OK; +} + +#ifdef I2C_INTERFACE +/** + * Change the I2C slave address which will be used during the transaction + * (For Debug Only) + * @param sad new slave address id + * @return OK + */ +int changeSAD(u8 sad) +{ + I2CSAD = sad; + return OK; +} +#endif + + +/** + * Retrieve the pointer to the device struct of the IC + * @return a the device struct pointer if client was previously set + * or NULL in all the other cases + */ +struct device *getDev(void) +{ + if (client != NULL) + return &(getClient()->dev); + else + return NULL; +} + + +#ifdef I2C_INTERFACE +/** + * Retrieve the pointer of the i2c_client struct representing the IC as i2c + * slave + * @return client if it was previously set or NULL in all the other cases + */ +struct i2c_client *getClient() +{ + if (client != NULL) + return (struct i2c_client *)client; + else + return NULL; +} +#else +/** + * Retrieve the pointer of the spi_device struct representing the IC as spi + * slave + * @return client if it was previously set or NULL in all the other cases + */ +struct spi_device *getClient() +{ + if (client != NULL) + return (struct spi_device *)client; + else + return NULL; +} +#endif + +struct fts_ts_info *getDrvInfo(void) +{ + struct device *dev = getDev(); + struct fts_ts_info *info = NULL; + + if (dev != NULL) + info = dev_get_drvdata(dev); + return info; +} + +/****************** New I2C API *********************/ + +/** + * Perform a direct bus read + * @param outBuf pointer of a byte array which should contain the byte read + * from the IC + * @param byteToRead number of bytes to read + * @return OK if success or an error code which specify the type of error + */ +static int fts_read_internal(u8 *outBuf, int byteToRead, bool dma_safe) +{ + int ret = -1; + int retry = 0; + struct fts_ts_info *info = getDrvInfo(); +#ifdef I2C_INTERFACE + struct i2c_msg I2CMsg[1]; +#else + struct spi_message msg; + struct spi_transfer transfer[1] = { { 0 } }; +#endif + + if (dma_safe == false && byteToRead > sizeof(info->io_read_buf)) { + pr_err("%s: preallocated buffers are too small!\n", __func__); + return ERROR_ALLOC; + } + +#ifdef I2C_INTERFACE + I2CMsg[0].addr = (__u16)I2CSAD; + I2CMsg[0].flags = (__u16)I2C_M_RD; + I2CMsg[0].len = (__u16)byteToRead; + if (dma_safe == false) + I2CMsg[0].buf = (__u8 *)info->io_read_buf; + else + I2CMsg[0].buf = (__u8 *)outBuf; +#else + spi_message_init(&msg); + + transfer[0].len = byteToRead; + transfer[0].delay_usecs = SPI_DELAY_CS; + transfer[0].tx_buf = NULL; + if (dma_safe == false) + transfer[0].rx_buf = info->io_read_buf; + else + transfer[0].rx_buf = outBuf; + spi_message_add_tail(&transfer[0], &msg); +#endif + + if (client == NULL) + return ERROR_BUS_O; + while (retry < I2C_RETRY && ret < OK) { +#ifdef I2C_INTERFACE + ret = i2c_transfer(getClient()->adapter, I2CMsg, 1); +#else + ret = spi_sync(getClient(), &msg); +#endif + + retry++; + if (ret < OK) + mdelay(I2C_WAIT_BEFORE_RETRY); + /* pr_err("fts_writeCmd: attempt %d\n", retry); */ + } + if (ret < 0) { + pr_err("%s: ERROR %08X\n", __func__, ERROR_BUS_R); + return ERROR_BUS_R; + } + + if (dma_safe == false) + memcpy(outBuf, info->io_read_buf, byteToRead); + + return OK; +} + + +/** + * Perform a bus write followed by a bus read without a stop condition + * @param cmd byte array containing the command to write + * @param cmdLength size of cmd + * @param outBuf pointer of a byte array which should contain the bytes read + * from the IC + * @param byteToRead number of bytes to read + * @return OK if success or an error code which specify the type of error + */ +static int fts_writeRead_internal(u8 *cmd, int cmdLength, u8 *outBuf, + int byteToRead, bool dma_safe) +{ + int ret = -1; + int retry = 0; + struct fts_ts_info *info = getDrvInfo(); +#ifdef I2C_INTERFACE + struct i2c_msg I2CMsg[2]; +#else + struct spi_message msg; + struct spi_transfer transfer[2] = { { 0 }, { 0 } }; +#endif + + if (dma_safe == false && (cmdLength > sizeof(info->io_write_buf) || + byteToRead > sizeof(info->io_read_buf))) { + pr_err("%s: preallocated buffers are too small!\n", __func__); + return ERROR_ALLOC; + } + +#ifdef I2C_INTERFACE + if (dma_safe == false) { + memcpy(info->io_write_buf, cmd, cmdLength); + cmd = info->io_write_buf; + } + + /* write msg */ + I2CMsg[0].addr = (__u16)I2CSAD; + I2CMsg[0].flags = (__u16)0; + I2CMsg[0].len = (__u16)cmdLength; + I2CMsg[0].buf = (__u8 *)cmd; + + /* read msg */ + I2CMsg[1].addr = (__u16)I2CSAD; + I2CMsg[1].flags = I2C_M_RD; + I2CMsg[1].len = byteToRead; + if (dma_safe == false) + I2CMsg[1].buf = (__u8 *)info->io_read_buf; + else + I2CMsg[1].buf = (__u8 *)outBuf; +#else + if (dma_safe == false) { + memcpy(info->io_write_buf, cmd, cmdLength); + cmd = info->io_write_buf; + } + + spi_message_init(&msg); + + transfer[0].len = cmdLength; + transfer[0].tx_buf = cmd; + transfer[0].rx_buf = NULL; + spi_message_add_tail(&transfer[0], &msg); + + transfer[1].len = byteToRead; + transfer[1].delay_usecs = SPI_DELAY_CS; + transfer[1].tx_buf = NULL; + if (dma_safe == false) + transfer[1].rx_buf = info->io_read_buf; + else + transfer[1].rx_buf = outBuf; + spi_message_add_tail(&transfer[1], &msg); + +#endif + + if (client == NULL) + return ERROR_BUS_O; + + while (retry < I2C_RETRY && ret < OK) { +#ifdef I2C_INTERFACE + ret = i2c_transfer(getClient()->adapter, I2CMsg, 2); +#else + ret = spi_sync(getClient(), &msg); +#endif + + retry++; + if (ret < OK) + mdelay(I2C_WAIT_BEFORE_RETRY); + } + if (ret < 0) { + pr_err("%s: ERROR %08X\n", __func__, ERROR_BUS_WR); + return ERROR_BUS_WR; + } + + if (dma_safe == false) + memcpy(outBuf, info->io_read_buf, byteToRead); + + return OK; +} + + +/** + * Perform a bus write + * @param cmd byte array containing the command to write + * @param cmdLength size of cmd + * @return OK if success or an error code which specify the type of error + */ +static int fts_write_internal(u8 *cmd, int cmdLength, bool dma_safe) +{ + int ret = -1; + int retry = 0; + struct fts_ts_info *info = getDrvInfo(); +#ifdef I2C_INTERFACE + struct i2c_msg I2CMsg[1]; +#else + struct spi_message msg; + struct spi_transfer transfer[1] = { { 0 } }; +#endif + + if (dma_safe == false && cmdLength > sizeof(info->io_write_buf)) { + pr_err("%s: preallocated buffers are too small!\n", __func__); + return ERROR_ALLOC; + } + +#ifdef I2C_INTERFACE + if (dma_safe == false) { + memcpy(info->io_write_buf, cmd, cmdLength); + cmd = info->io_write_buf; + } + + I2CMsg[0].addr = (__u16)I2CSAD; + I2CMsg[0].flags = (__u16)0; + I2CMsg[0].len = (__u16)cmdLength; + I2CMsg[0].buf = (__u8 *)cmd; +#else + if (dma_safe == false) { + memcpy(info->io_write_buf, cmd, cmdLength); + cmd = info->io_write_buf; + } + + spi_message_init(&msg); + + transfer[0].len = cmdLength; + transfer[0].delay_usecs = SPI_DELAY_CS; + transfer[0].tx_buf = cmd; + transfer[0].rx_buf = NULL; + spi_message_add_tail(&transfer[0], &msg); +#endif + + + if (client == NULL) + return ERROR_BUS_O; + while (retry < I2C_RETRY && ret < OK) { +#ifdef I2C_INTERFACE + ret = i2c_transfer(getClient()->adapter, I2CMsg, 1); +#else + ret = spi_sync(getClient(), &msg); +#endif + + retry++; + if (ret < OK) + mdelay(I2C_WAIT_BEFORE_RETRY); + /* pr_err("fts_writeCmd: attempt %d\n", retry); */ + } + if (ret < 0) { + pr_err("%s: ERROR %08X\n", __func__, ERROR_BUS_W); + return ERROR_BUS_W; + } + return OK; +} + +/** + * Write a FW command to the IC and check automatically the echo event + * @param cmd byte array containing the command to send + * @param cmdLength size of cmd + * @return OK if success, or an error code which specify the type of error + */ +static int fts_writeFwCmd_internal(u8 *cmd, int cmdLength, bool dma_safe) +{ + int ret = -1; + int ret2 = -1; + int retry = 0; + struct fts_ts_info *info = getDrvInfo(); +#ifdef I2C_INTERFACE + struct i2c_msg I2CMsg[1]; +#else + struct spi_message msg; + struct spi_transfer transfer[1] = { { 0 } }; +#endif + + if (dma_safe == false && cmdLength > sizeof(info->io_write_buf)) { + pr_err("%s: preallocated buffers are too small!\n", __func__); + return ERROR_ALLOC; + } + +#ifdef I2C_INTERFACE + if (dma_safe == false) { + memcpy(info->io_write_buf, cmd, cmdLength); + cmd = info->io_write_buf; + } + + I2CMsg[0].addr = (__u16)I2CSAD; + I2CMsg[0].flags = (__u16)0; + I2CMsg[0].len = (__u16)cmdLength; + I2CMsg[0].buf = (__u8 *)cmd; +#else + if (dma_safe == false) { + memcpy(info->io_write_buf, cmd, cmdLength); + cmd = info->io_write_buf; + } + + spi_message_init(&msg); + + transfer[0].len = cmdLength; + transfer[0].delay_usecs = SPI_DELAY_CS; + transfer[0].tx_buf = cmd; + transfer[0].rx_buf = NULL; + spi_message_add_tail(&transfer[0], &msg); +#endif + + if (client == NULL) + return ERROR_BUS_O; + resetErrorList(); + while (retry < I2C_RETRY && (ret < OK || ret2 < OK)) { +#ifdef I2C_INTERFACE + ret = i2c_transfer(getClient()->adapter, I2CMsg, 1); +#else + ret = spi_sync(getClient(), &msg); +#endif + retry++; + if (ret >= 0) + ret2 = checkEcho(cmd, cmdLength); + if (ret < OK || ret2 < OK) + mdelay(I2C_WAIT_BEFORE_RETRY); + /* pr_err("fts_writeCmd: attempt %d\n", retry); */ + } + if (ret < 0) { + pr_err("fts_writeFwCmd: ERROR %08X\n", ERROR_BUS_W); + return ERROR_BUS_W; + } + if (ret2 < OK) { + pr_err("fts_writeFwCmd: check echo ERROR %08X\n", ret2); + return ret2; + } + return OK; +} + + +/** + * Perform two bus write and one bus read without any stop condition + * In case of FTI this function is not supported and the same sequence + * can be achieved calling fts_write followed by an fts_writeRead. + * @param writeCmd1 byte array containing the first command to write + * @param writeCmdLength size of writeCmd1 + * @param readCmd1 byte array containing the second command to write + * @param readCmdLength size of readCmd1 + * @param outBuf pointer of a byte array which should contain the bytes read + * from the IC + * @param byteToRead number of bytes to read + * @return OK if success or an error code which specify the type of error + */ +static int fts_writeThenWriteRead_internal(u8 *writeCmd1, int writeCmdLength, + u8 *readCmd1, int readCmdLength, + u8 *outBuf, int byteToRead, + bool dma_safe) +{ + int ret = -1; + int retry = 0; + struct fts_ts_info *info = getDrvInfo(); +#ifdef I2C_INTERFACE + struct i2c_msg I2CMsg[3]; +#else + struct spi_message msg; + struct spi_transfer transfer[3] = { { 0 }, { 0 }, { 0 } }; +#endif + + if (dma_safe == false && (writeCmdLength > sizeof(info->io_write_buf) || + readCmdLength > sizeof(info->io_extra_write_buf) || + byteToRead > sizeof(info->io_read_buf))) { + pr_err("%s: preallocated buffers are too small!\n", __func__); + return ERROR_ALLOC; + } + +#ifdef I2C_INTERFACE + if (dma_safe == false) { + memcpy(info->io_write_buf, writeCmd1, writeCmdLength); + writeCmd1 = info->io_write_buf; + memcpy(info->io_extra_write_buf, readCmd1, readCmdLength); + readCmd1 = info->io_extra_write_buf; + } + + /* write msg */ + I2CMsg[0].addr = (__u16)I2CSAD; + I2CMsg[0].flags = (__u16)0; + I2CMsg[0].len = (__u16)writeCmdLength; + I2CMsg[0].buf = (__u8 *)writeCmd1; + + /* write msg */ + I2CMsg[1].addr = (__u16)I2CSAD; + I2CMsg[1].flags = (__u16)0; + I2CMsg[1].len = (__u16)readCmdLength; + I2CMsg[1].buf = (__u8 *)readCmd1; + + /* read msg */ + I2CMsg[2].addr = (__u16)I2CSAD; + I2CMsg[2].flags = I2C_M_RD; + I2CMsg[2].len = byteToRead; + if (dma_safe == false) + I2CMsg[2].buf = (__u8 *)info->io_read_buf; + else + I2CMsg[2].buf = (__u8 *)outBuf; +#else + if (dma_safe == false) { + memcpy(info->io_write_buf, writeCmd1, writeCmdLength); + writeCmd1 = info->io_write_buf; + memcpy(info->io_extra_write_buf, readCmd1, readCmdLength); + readCmd1 = info->io_extra_write_buf; + } + + spi_message_init(&msg); + + transfer[0].len = writeCmdLength; + transfer[0].tx_buf = writeCmd1; + transfer[0].rx_buf = NULL; + spi_message_add_tail(&transfer[0], &msg); + + transfer[1].len = readCmdLength; + transfer[1].tx_buf = readCmd1; + transfer[1].rx_buf = NULL; + spi_message_add_tail(&transfer[1], &msg); + + transfer[2].len = byteToRead; + transfer[2].delay_usecs = SPI_DELAY_CS; + transfer[2].tx_buf = NULL; + if (dma_safe == false) + transfer[2].rx_buf = info->io_read_buf; + else + transfer[2].rx_buf = outBuf; + spi_message_add_tail(&transfer[2], &msg); +#endif + + if (client == NULL) + return ERROR_BUS_O; + while (retry < I2C_RETRY && ret < OK) { +#ifdef I2C_INTERFACE + ret = i2c_transfer(getClient()->adapter, I2CMsg, 3); +#else + ret = spi_sync(getClient(), &msg); +#endif + retry++; + if (ret < OK) + mdelay(I2C_WAIT_BEFORE_RETRY); + } + + if (ret < 0) { + pr_err("%s: ERROR %08X\n", __func__, ERROR_BUS_WR); + return ERROR_BUS_WR; + } + + if (dma_safe == false) + memcpy(outBuf, info->io_read_buf, byteToRead); + + return OK; +} + +/* Wrapper API for i2c read and write */ +int fts_read(u8 *outBuf, int byteToRead) +{ + return fts_read_internal(outBuf, byteToRead, false); +} + +int fts_read_heap(u8 *outBuf, int byteToRead) +{ + return fts_read_internal(outBuf, byteToRead, true); +} + +int fts_writeRead(u8 *cmd, int cmdLength, u8 *outBuf, int byteToRead) +{ + return fts_writeRead_internal(cmd, cmdLength, outBuf, byteToRead, + false); +} + +int fts_writeRead_heap(u8 *cmd, int cmdLength, u8 *outBuf, int byteToRead) +{ + return fts_writeRead_internal(cmd, cmdLength, outBuf, byteToRead, true); +} + +int fts_write(u8 *cmd, int cmdLength) +{ + return fts_write_internal(cmd, cmdLength, false); +} + +int fts_write_heap(u8 *cmd, int cmdLength) +{ + return fts_write_internal(cmd, cmdLength, true); +} + +int fts_writeFwCmd(u8 *cmd, int cmdLength) +{ + return fts_writeFwCmd_internal(cmd, cmdLength, false); +} + +int fts_writeFwCmd_heap(u8 *cmd, int cmdLength) +{ + return fts_writeFwCmd_internal(cmd, cmdLength, true); +} + +int fts_writeThenWriteRead(u8 *writeCmd1, int writeCmdLength, + u8 *readCmd1, int readCmdLength, + u8 *outBuf, int byteToRead) +{ + return fts_writeThenWriteRead_internal(writeCmd1, writeCmdLength, + readCmd1, readCmdLength, + outBuf, byteToRead, false); +} + +int fts_writeThenWriteRead_heap(u8 *writeCmd1, int writeCmdLength, + u8 *readCmd1, int readCmdLength, + u8 *outBuf, int byteToRead) +{ + return fts_writeThenWriteRead_internal(writeCmd1, writeCmdLength, + readCmd1, readCmdLength, + outBuf, byteToRead, true); +} + +/** + * Perform a chunked write with one byte op code and 1 to 8 bytes address + * @param cmd byte containing the op code to write + * @param addrSize address size in byte + * @param address the starting address + * @param data pointer of a byte array which contain the bytes to write + * @param dataSize size of data + * @return OK if success or an error code which specify the type of error + */ +/* this function works only if the address is max 8 bytes */ +int fts_writeU8UX(u8 cmd, AddrSize addrSize, u64 address, u8 *data, + int dataSize) +{ + u8 *finalCmd; + int remaining = dataSize; + int toWrite = 0, i = 0; + struct fts_ts_info *info = getDrvInfo(); + + finalCmd = info->io_write_buf; + + if (addrSize <= sizeof(u64)) { + while (remaining > 0) { + if (remaining >= WRITE_CHUNK) { + toWrite = WRITE_CHUNK; + remaining -= WRITE_CHUNK; + } else { + toWrite = remaining; + remaining = 0; + } + + finalCmd[0] = cmd; + pr_debug("%s: addrSize = %d\n", __func__, addrSize); + for (i = 0; i < addrSize; i++) { + finalCmd[i + 1] = (u8)((address >> ((addrSize - + 1 - i) * + 8)) & 0xFF); + pr_debug("%s: cmd[%d] = %02X\n", + __func__, i + 1, finalCmd[i + 1]); + } + + memcpy(&finalCmd[addrSize + 1], data, toWrite); + + if (fts_write_heap(finalCmd, 1 + addrSize + toWrite) + < OK) { + pr_err(" %s: ERROR %08X\n", + __func__, ERROR_BUS_W); + return ERROR_BUS_W; + } + + address += toWrite; + + data += toWrite; + } + } else + pr_err("%s: address size bigger than max allowed %lu... ERROR %08X\n", + __func__, sizeof(u64), ERROR_OP_NOT_ALLOW); + + return OK; +} + +/** + * Perform a chunked write read with one byte op code and 1 to 8 bytes address + * and dummy byte support. + * @param cmd byte containing the op code to write + * @param addrSize address size in byte + * @param address the starting address + * @param outBuf pointer of a byte array which contain the bytes to read + * @param byteToRead number of bytes to read + * @param hasDummyByte if the first byte of each reading is dummy (must be + * skipped) + * set to 1, otherwise if it is valid set to 0 (or any other value) + * @return OK if success or an error code which specify the type of error + */ +int fts_writeReadU8UX(u8 cmd, AddrSize addrSize, u64 address, u8 *outBuf, + int byteToRead, int hasDummyByte) +{ + u8 *finalCmd; + u8 *buff; + int remaining = byteToRead; + int toRead = 0, i = 0; + struct fts_ts_info *info = getDrvInfo(); + + finalCmd = info->io_write_buf; + buff = info->io_read_buf; + + while (remaining > 0) { + if (remaining >= READ_CHUNK) { + toRead = READ_CHUNK; + remaining -= READ_CHUNK; + } else { + toRead = remaining; + remaining = 0; + } + + finalCmd[0] = cmd; + for (i = 0; i < addrSize; i++) + finalCmd[i + 1] = (u8)((address >> ((addrSize - 1 - i) * + 8)) & 0xFF); + + if (hasDummyByte == 1) { + if (fts_writeRead_heap(finalCmd, 1 + addrSize, buff, + toRead + 1) < OK) { + pr_err("%s: read error... ERROR %08X\n", + __func__, ERROR_BUS_WR); + return ERROR_BUS_WR; + } + memcpy(outBuf, buff + 1, toRead); + } else { + if (fts_writeRead_heap(finalCmd, 1 + addrSize, buff, + toRead) < OK) { + pr_err("%s: read error... ERROR %08X\n", + __func__, ERROR_BUS_WR); + return ERROR_BUS_WR; + } + memcpy(outBuf, buff, toRead); + } + + address += toRead; + + outBuf += toRead; + } + + return OK; +} + +/** + * Perform a chunked write followed by a second write with one byte op code + * for each write and 1 to 8 bytes address (the sum of the 2 address size of + * the two writes can not exceed 8 bytes) + * @param cmd1 byte containing the op code of first write + * @param addrSize1 address size in byte of first write + * @param cmd2 byte containing the op code of second write + * @param addrSize2 address size in byte of second write + * @param address the starting address + * @param data pointer of a byte array which contain the bytes to write + * @param dataSize size of data + * @return OK if success or an error code which specify the type of error + */ +/* this function works only if the sum of two addresses in the two commands is + * max 8 bytes */ +int fts_writeU8UXthenWriteU8UX(u8 cmd1, AddrSize addrSize1, u8 cmd2, + AddrSize addrSize2, u64 address, u8 *data, + int dataSize) +{ + u8 *finalCmd1; + u8 *finalCmd2; + int remaining = dataSize; + int toWrite = 0, i = 0; + struct fts_ts_info *info = getDrvInfo(); + + finalCmd1 = info->io_write_buf; + finalCmd2 = info->io_extra_write_buf; + + while (remaining > 0) { + if (remaining >= WRITE_CHUNK) { + toWrite = WRITE_CHUNK; + remaining -= WRITE_CHUNK; + } else { + toWrite = remaining; + remaining = 0; + } + + finalCmd1[0] = cmd1; + for (i = 0; i < addrSize1; i++) + finalCmd1[i + 1] = (u8)((address >> ((addrSize1 + + addrSize2 - 1 - + i) * 8)) & 0xFF); + + finalCmd2[0] = cmd2; + for (i = addrSize1; i < addrSize1 + addrSize2; i++) + finalCmd2[i - addrSize1 + 1] = (u8)((address >> + ((addrSize1 + + addrSize2 - 1 - + i) * 8)) & 0xFF); + + memcpy(&finalCmd2[addrSize2 + 1], data, toWrite); + + if (fts_write_heap(finalCmd1, 1 + addrSize1) < OK) { + pr_err("%s: first write error... ERROR %08X\n", + __func__, ERROR_BUS_W); + return ERROR_BUS_W; + } + + if (fts_write_heap(finalCmd2, 1 + addrSize2 + toWrite) < OK) { + pr_err("%s: second write error... ERROR %08X\n", + __func__, ERROR_BUS_W); + return ERROR_BUS_W; + } + + address += toWrite; + + data += toWrite; + } + + return OK; +} + +/** + * Perform a chunked write followed by a write read with one byte op code + * and 1 to 8 bytes address for each write and dummy byte support. + * @param cmd1 byte containing the op code of first write + * @param addrSize1 address size in byte of first write + * @param cmd2 byte containing the op code of second write read + * @param addrSize2 address size in byte of second write read + * @param address the starting address + * @param outBuf pointer of a byte array which contain the bytes to read + * @param byteToRead number of bytes to read + * @param hasDummyByte if the first byte of each reading is dummy (must be + * skipped) set to 1, + * otherwise if it is valid set to 0 (or any other value) + * @return OK if success or an error code which specify the type of error + */ +/* this function works only if the sum of two addresses in the two commands is + * max 8 bytes */ +int fts_writeU8UXthenWriteReadU8UX(u8 cmd1, AddrSize addrSize1, u8 cmd2, + AddrSize addrSize2, u64 address, u8 *outBuf, + int byteToRead, int hasDummyByte) +{ + u8 *finalCmd1; + u8 *finalCmd2; + u8 *buff; + int remaining = byteToRead; + int toRead = 0, i = 0; + struct fts_ts_info *info = getDrvInfo(); + + finalCmd1 = info->io_write_buf; + finalCmd2 = info->io_extra_write_buf; + buff = info->io_read_buf; + + while (remaining > 0) { + if (remaining >= READ_CHUNK) { + toRead = READ_CHUNK; + remaining -= READ_CHUNK; + } else { + toRead = remaining; + remaining = 0; + } + + + finalCmd1[0] = cmd1; + for (i = 0; i < addrSize1; i++) + finalCmd1[i + 1] = (u8)((address >> ((addrSize1 + + addrSize2 - 1 - + i) * 8)) & 0xFF); + + finalCmd2[0] = cmd2; + for (i = addrSize1; i < addrSize1 + addrSize2; i++) + finalCmd2[i - addrSize1 + 1] = (u8)((address >> + ((addrSize1 + + addrSize2 - 1 - + i) * 8)) & 0xFF); + + if (fts_write_heap(finalCmd1, 1 + addrSize1) < OK) { + pr_err("%s: first write error... ERROR %08X\n", + __func__, ERROR_BUS_W); + return ERROR_BUS_W; + } + + if (hasDummyByte == 1) { + if (fts_writeRead_heap(finalCmd2, 1 + addrSize2, buff, + toRead + 1) < OK) { + pr_err("%s: read error... ERROR %08X\n", + __func__, ERROR_BUS_WR); + return ERROR_BUS_WR; + } + memcpy(outBuf, buff + 1, toRead); + } else { + if (fts_writeRead_heap(finalCmd2, 1 + addrSize2, buff, + toRead) < OK) { + pr_err("%s: read error... ERROR %08X\n", + __func__, ERROR_BUS_WR); + return ERROR_BUS_WR; + } + memcpy(outBuf, buff, toRead); + } + + address += toRead; + + outBuf += toRead; + } + + return OK; +} diff --git a/fts_lib/ftsIO.h b/fts_lib/ftsIO.h new file mode 100644 index 0000000..08de4d4 --- /dev/null +++ b/fts_lib/ftsIO.h @@ -0,0 +1,80 @@ +/* + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * I2C/SPI Communication * + * * + ************************************************************************** + ************************************************************************** + * + */ +/*! + * \file ftsIO.h + * \brief Contains all the definitions and prototypes used and implemented in + * ftsIO.c + */ + +#ifndef FTS_IO_H +#define FTS_IO_H + +#include "ftsSoftware.h" + +#define I2C_RETRY 3 /* /< number of retry in case of i2c + * failure */ +#define I2C_WAIT_BEFORE_RETRY 2 /* /< wait in ms before retry an i2c + * transaction */ + +#ifdef I2C_INTERFACE +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +struct i2c_client *getClient(void); +#else +#include <linux/spi/spi.h> +struct spi_device *getClient(void); +#endif + + + +int openChannel(void *clt); +struct device *getDev(void); + + + +/*************** NEW I2C API ****************/ +#ifdef I2C_INTERFACE +int changeSAD(u8 sad); +#endif +int fts_read(u8 *outBuf, int byteToRead); +int fts_read_heap(u8 *outBuf, int byteToRead); +int fts_writeRead(u8 *cmd, int cmdLength, u8 *outBuf, int byteToRead); +int fts_writeRead_heap(u8 *cmd, int cmdLength, u8 *outBuf, int byteToRead); +int fts_write(u8 *cmd, int cmdLength); +int fts_write_heap(u8 *cmd, int cmdLength); +int fts_writeFwCmd(u8 *cmd, int cmdLength); +int fts_writeFwCmd_heap(u8 *cmd, int cmdLength); +int fts_writeThenWriteRead(u8 *writeCmd1, int writeCmdLength, + u8 *readCmd1, int readCmdLength, + u8 *outBuf, int byteToRead); +int fts_writeThenWriteRead_heap(u8 *writeCmd1, int writeCmdLength, + u8 *readCmd1, int readCmdLength, + u8 *outBuf, int byteToRead); + +/* chunked version of fts_write */ +int fts_writeU8UX(u8 cmd, AddrSize addrSize, u64 address, u8 *data, + int dataSize); +/* chunked version of fts_writeRead */ +int fts_writeReadU8UX(u8 cmd, AddrSize addrSize, u64 address, u8 *outBuf, + int byteToRead, int hasDummyByte); +/* chunked, write followed by another write */ +int fts_writeU8UXthenWriteU8UX(u8 cmd1, AddrSize addrSize1, u8 cmd2, + AddrSize addrSize2, u64 address, u8 *data, + int dataSize); +/* chunked, write followed by a writeRead */ +int fts_writeU8UXthenWriteReadU8UX(u8 cmd1, AddrSize addrSize1, u8 cmd2, + AddrSize addrSize2, u64 address, u8 *outBuf, + int count, int hasDummyByte); +#endif diff --git a/fts_lib/ftsSoftware.h b/fts_lib/ftsSoftware.h new file mode 100644 index 0000000..6aa9e18 --- /dev/null +++ b/fts_lib/ftsSoftware.h @@ -0,0 +1,557 @@ +/* + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FW related data * + * * + ************************************************************************** + ************************************************************************** + * + */ + +/*! + * \file ftsSoftware.h + * \brief Contains all the definitions and information related to the IC + * from a fw/driver point of view + */ + + +#ifndef FTS_SOFTWARE_H +#define FTS_SOFTWARE_H +#include <linux/types.h> +#include "ftsHardware.h" + +#define SUPPORT_PROX_PALM + +/* signed type */ +typedef signed char i8; /* /< basic type that represent one signed byte (or 8 + * bits) */ + +/** + * Enumerator which contains all the possible address length expressed in + *bytes. + */ +typedef enum { + NO_ADDR = 0, + BITS_8 = 1, + BITS_16 = 2, + BITS_24 = 3, + BITS_32 = 4, + BITS_40 = 5, + BITS_48 = 6, + BITS_56 = 7, + BITS_64 = 8 +} AddrSize; + + +/******************** NEW API *********************/ + +/* HOST COMMAND */ +/** @defgroup host_command Fw Host op codes + * Valid op codes for fw commands + * @{ + */ + +/** @defgroup scan_mode Scan Mode + * @ingroup host_command + * Set the scanning mode required according to the parameters + * @{ + */ +#define FTS_CMD_SCAN_MODE 0xA0 /* /< OP Code to set scan mode */ +/** @} */ + +/** @defgroup feat_sel Feature Select + * @ingroup host_command + * Set the system defined features to enable/disable according the parameters + * @{ + */ +#define FTS_CMD_FEATURE 0xA2 /* /< OP code to set features */ +/** @} */ + +/** @defgroup sys_cmd System Command + * @ingroup host_command + * Execute a system command to perform core tasks + * @{ + */ +#define FTS_CMD_SYSTEM 0xA4 /* /< OP code to write s system command + * */ +/** @} */ + +/** @} */ /* end host_command group */ + +/* SCAN MODE OPTION (0xA0) */ +/* Scan mode selection */ +/** @defgroup scan_opt Scan Mode Option + * @ingroup scan_mode + * Valid scanning modes and their options + * @{ + */ +#define SCAN_MODE_ACTIVE 0x00 /* /< Select the Active scanning mode */ +#define SCAN_MODE_LOW_POWER 0x01 /* /< Select the low power scanning mode + * */ +#define SCAN_MODE_JIG_1 0x02 /* /< Select the Jig test 1 */ +#define SCAN_MODE_LOCKED 0x03 /* /< Select the Scan mode which will be + * locked */ +/** @}*/ + +/* Active mode option (bitmask) */ +/** @defgroup active_bitmask Active Mode Bitmask + * @ingroup scan_opt + * Bitmask to use to enables the specific scanning with the SCAN_MODE_ACTIVE + * option + * @{ + */ +#define ACTIVE_MULTI_TOUCH 0x01 /* /< Bit 0 MS/SS scan */ +#define ACTIVE_KEY 0x02 /* /< Bit 1 Key scan */ +#define ACTIVE_HOVER 0x04 /* /< Bit 2 Hover scan */ +#define ACTIVE_PROXIMITY 0x08 /* /< Bit 3 Proximity scan */ +#define ACTIVE_FORCE 0x10 /* /< Bit 4 Force scan */ +/** @}*/ + +/* Locked mode option (locked mode) */ +/** @defgroup locked_opt Locked Mode Option + * @ingroup scan_opt + * Options to enable and lock specific scanning with the SCAN_MODE_LOCKED + * option + * @{ + */ +#define LOCKED_ACTIVE 0x00 /* /< Active Scan Mode */ +#define LOCKED_HOVER 0x01 /* /< Hover Scan Mode */ +#define LOCKED_IDLE 0x02 /* /< Idle Scan Mode */ +#define LOCKED_ONLY_SELF 0x03 /* /< Only Self Sense scan mode */ +#define LOCKED_ONLY_MUTUAL_0 0x04 /* /< Only Mutual Sense scan mode at + * lowest freq */ +#define LOCKED_ONLY_MUTUAL_1 0x05 /* /< Only Mutual Sense scan mode */ +#define LOCKED_ONLY_MUTUAL_2 0x06 /* /< Only Mutual Sense scan mode */ +#define LOCKED_ONLY_MUTUAL_3 0x07 /* /< Only Mutual Sense scan mode at + * highest freq */ +#define LOCKED_SINGLE_ENDED_ONLY_MUTUAL_0 0x20 /* /< Only Mutual Sense + * scan mode at lowest + * freq in single ended + * mode + */ +#define LOCKED_LP_DETECT 0x10 /* /< Low Power SS */ +#define LOCKED_LP_ACTIVE 0x11 /* /< Low Power MS */ +/** @}*/ + +/* FEATURE SELECT OPTION (0xA2) */ +/* Feature Selection */ +/** @defgroup feat_opt Feature Selection Option + * @ingroup feat_sel + * System defined features that can be enable/disable + * @{ + */ +#define FEAT_SEL_GLOVE 0x00 /* /< Glove Mode */ +#define FEAT_SEL_COVER 0x01 /* /< Cover Mode */ +#define FEAT_SEL_CHARGER 0x02 /* /< Charger Mode */ +#define FEAT_SEL_GESTURE 0x03 /* /< Gesture Mode */ +#define FEAT_SEL_GRIP 0x04 /* /< Grip Detection */ +#define FEAT_SEL_STYLUS 0x07 /* /< Stylus Mode + * (this is a driver define, not + * available in FW) */ +/** @}*/ + +/* Feature Settings */ +#define FEAT_ENABLE 1 /* /< General value to enable a feature + * */ +#define FEAT_DISABLE 0 /* /< General value to disable a feature + * */ + + +/* Charger */ +/** @defgroup charger_opt Charger Mode Option + * @ingroup feat_sel + * Option for Charger Mode, it is a bitmask where the each bit indicate a + * different kind of chager + * @{ + */ +#define CHARGER_CABLE 0x01 /* /< normal usb charger */ +#define CHARGER_WIRLESS 0x02 /* /< wireless charger */ +/** @}*/ + +/* Gestures */ +/** @defgroup gesture_opt Gesture Mode Option + * @ingroup feat_sel + * Gesture IDs of the predefined gesture recognized by the fw. + * The ID represent also the position of the corresponding bit in the gesture + * mask + * @{ + */ +#define GEST_ID_UP_1F 0x01 /* /< Bottom to Top line */ +#define GEST_ID_DOWN_1F 0x02 /* /< Top to bottom line */ +#define GEST_ID_LEFT_1F 0x03 /* /< Right to left line */ +#define GEST_ID_RIGHT_1F 0x04 /* /< Left to right line */ +#define GEST_ID_DBLTAP 0x05 /* /< Double Tap */ +#define GEST_ID_O 0x06 /* /< 'O' */ +#define GEST_ID_C 0x07 /* /< 'C' */ +#define GEST_ID_M 0x08 /* /< 'M' */ +#define GEST_ID_W 0x09 /* /< 'W' */ +#define GEST_ID_E 0x0A /* /< 'e' */ +#define GEST_ID_L 0x0B /* /< 'L' */ +#define GEST_ID_F 0x0C /* /< 'F' */ +#define GEST_ID_V 0x0D /* /< 'V' */ +#define GEST_ID_AT 0x0E /* /< '@' */ +#define GEST_ID_S 0x0F /* /< 'S' */ +#define GEST_ID_Z 0x10 /* /< 'Z' */ +#define GEST_ID_LEFTBRACE 0x11 /* /< '<' */ +#define GEST_ID_RIGHTBRACE 0x12 /* /< '>' */ +#define GEST_ID_CARET 0x13 /* /< '^' */ +/** @}*/ + +/* WRYTE SYSTEM COMMAND (0xA4) */ +/* System command */ +/** @defgroup sys_opt System Command Option + * @ingroup sys_cmd + * Valid System Command Parameters + * @{ + */ +#define SYS_CMD_SPECIAL 0x00 /* /< Special Commands */ +#define SYS_CMD_INT 0x01 /* /< FW Interrupt Control */ +#define SYS_CMD_FORCE_CAL 0x02 /* /< Force Calibration */ +#define SYS_CMD_CX_TUNING 0x03 /* /< CX initialization */ +#define SYS_CMD_ITO 0x04 /* /< ITO test */ +#define SYS_CMD_SAVE_FLASH 0x05 /* /< Saving to flash */ +#define SYS_CMD_LOAD_DATA 0x06 /* /< Load Host data memory */ +#define SYS_CMD_SPECIAL_TUNING 0x08 /* /< Perform some special tuning */ +#define SYS_CMD_MP_FLAG 0x0C /* /< Update value of MP flag in RAM */ +/** @} */ + +/* System command settings */ +/* Special commands */ +/** @defgroup sys_special_opt Special Command Option + * @ingroup sys_cmd + * Valid special command + * @{ + */ +#define SPECIAL_SYS_RESET 0x00 /* /< System Reset triggered by + * the FW */ +#define SPECIAL_FIFO_FLUSH 0x01 /* /< Flush of the FIFO */ +#define SPECIAL_PANEL_INIT 0x02 /* /< Panel Initialization */ +#define SPECIAL_FULL_PANEL_INIT 0x03 /* /< Full panel initialization + * */ +#define SPECIAL_WRITE_HOST_MEM_TO_FLASH 0x04 /* /< Write */ +/** @} */ + + +/* Force Cal and Cx auto tuning */ +/** @defgroup forcecal_cx_opt Force Cal and Tuning Option + * @ingroup sys_cmd + * Valid bitmask for triggering forcecal or performing manual autotune + * @{ + */ +#define CAL_MS_TOUCH 0x01 /* /< Mutual Sense Touch */ +#define CAL_MS_LOW_POWER 0x02 /* /< Mutual Sense Touch in low power + * mode */ +#define CAL_SS_TOUCH 0x04 /* /< Self Sense Touch */ +#define CAL_SS_IDLE 0x08 /* /< Self Sense Touch in idle mode */ +#define CAL_MS_KEY 0x10 /* /< Mutual Sense Key */ +#define CAL_SS_KEY 0x20 /* /< Self Sense Key */ +#define CAL_MS_FORCE 0x40 /* /< Mutual Sense Force */ +#define CAL_SS_FORCE 0x80 /* /< Self Sense Force */ +/** @} */ + +/* ITO checks (position of the bit in the mask) */ +/** @defgroup ito_opt ITO Test Option + * @ingroup sys_cmd + * Valid option for the ITO test + * @{ + */ +#define ITO_FORCE_OPEN 0x00 /* /< Check if some force channels is + * open */ +#define ITO_SENSE_OPEN 0x01 /* /< Check if some sense channels is + * open */ +#define ITO_FORCE_GROUND 0x02 /* /< Check if some force channels is + * short to ground */ +#define ITO_SENSE_GROUND 0x03 /* /< Check if some sense channels is + * short to ground */ +#define ITO_FORCE_VDD 0x04 /* /< Check if some force channels is + * short to VDD */ +#define ITO_SENSE_VDD 0x05 /* /< Check if some sense channels is + * short to VDD */ +#define ITO_FORCE_FORCE 0x06 /* /< Check force to force channels */ +#define ITO_FORCE_SENSE 0x07 /* /< Check force to sense channels */ +#define ITO_SENSE_SENSE 0x08 /* /< Check sense to sense channels */ +#define ITO_KEY_FORCE_OPEN 0x09 /* /< Check if some force channels used + * for the key is open */ +#define ITO_KEY_SENSE_OPEN 0x0A /* /< Check if some sense channels used + * for the key is open */ +/** @}*/ + +/* Save flash */ +/** @defgroup save_opt Save to Flash Option + * @ingroup sys_cmd + * Valid option for saving data to the Flash + * @{ + */ +#define SAVE_FW_CONF 0x01 /* /< Save the confing to the flash */ +#define SAVE_CX 0x02 /* /< Save the CX to the flash */ +#define SAVE_PANEL_CONF 0x04 /* /< Save the Panel configuration to the flash + * */ +/** @}*/ + +/* Load Data */ +/** @defgroup load_opt Load Host Data Option + * @ingroup sys_cmd + * Valid option to ask to the FW to load host data into the memory + * @{ + */ +#define LOAD_SYS_INFO 0x01 /* /< Load System Info */ +#define LOAD_CX_MS_TOUCH 0x10 /* /< Load MS Init Data for + * Active Mode */ +#define LOAD_CX_MS_LOW_POWER 0x11 /* /< Load MS Init Data for Low + * Power Mode */ +#define LOAD_CX_SS_TOUCH 0x12 /* /< Load SS Init Data for + * Active Mode */ +#define LOAD_CX_SS_TOUCH_IDLE 0x13 /* /< Load SS Init Data for Low + * Power Mode */ +#define LOAD_CX_MS_KEY 0x14 /* /< Load MS Init Data for Key + * */ +#define LOAD_CX_SS_KEY 0x15 /* /< Load SS Init Data for Key + * */ +#define LOAD_CX_MS_FORCE 0x16 /* /< Load MS Init Data for + * Force */ +#define LOAD_CX_SS_FORCE 0x17 /* /< Load SS Init Data for + * Force */ +#define LOAD_SYNC_FRAME_RAW 0x30 /* /< Load a Synchronized Raw + * Frame */ +#define LOAD_SYNC_FRAME_FILTER 0x31 /* /< Load a Synchronized Filter + * Frame */ +#define LOAD_SYNC_FRAME_STRENGTH 0x33 /* /< Load a Synchronized + * Strength Frame */ +#define LOAD_SYNC_FRAME_BASELINE 0x32 /* /< Load a Synchronized + * Baseline Frame */ +#define LOAD_PANEL_CX_TOT_MS_TOUCH 0x50 /* /< Load TOT MS Init Data for + * Active Mode */ +#define LOAD_PANEL_CX_TOT_MS_LOW_POWER 0x51 /* /< Load TOT MS Init Data for + * Low Power Mode */ +#define LOAD_PANEL_CX_TOT_SS_TOUCH 0x52 /* /< Load TOT SS Init Data for + * Active Mode */ +#define LOAD_PANEL_CX_TOT_SS_TOUCH_IDLE 0x53 /* /< Load TOT SS Init Data for + * Low Power Mode */ +#define LOAD_PANEL_CX_TOT_MS_KEY 0x54 /* /< Load TOT MS Init Data for + * Key */ +#define LOAD_PANEL_CX_TOT_SS_KEY 0x55 /* /< Load TOT SS Init Data for + * Key */ +#define LOAD_PANEL_CX_TOT_MS_FORCE 0x56 /* /< Load TOT MS Init Data for + * Force */ +#define LOAD_PANEL_CX_TOT_SS_FORCE 0x57 /* /< Load TOT SS Init Data for + * Force */ +#define LOAD_SENS_CAL_COEFF 0xC0 /* /< Load Sesitivity + * Calibration Coefficients */ +#define LOAD_GOLDEN_MUTUAL_RAW 0x80 /* /< Load Goden Mutual Raw Data */ + +/** @}*/ + +/* Special Tuning */ +/** @defgroup spcl_tun_opt Special Tuning Option + * @ingroup sys_cmd + * Valid special tuning operations which the fw can perform (bitmask) + * @{ + */ +#define SPECIAL_TUNING_LP_TIMER 0x01 /* /< Perform LP Timer calibration */ +#define SPECIAL_TUNING_IOFF 0x02 /* /< Perform Ioff calibration */ + +/** @}*/ + +/* EVENT ID */ +/** @defgroup events_group FW Event IDs and Types + * Event IDs and Types pushed by the FW into the FIFO + * @{ + */ +#define EVT_ID_NOEVENT 0x00 /* /< No Events */ +#define EVT_ID_CONTROLLER_READY 0x03 /* /< Controller ready, issued after a + * system reset. */ +#define EVT_ID_ENTER_POINT 0x13 /* /< Touch enter in the sensing area */ +#define EVT_ID_MOTION_POINT 0x23 /* /< Touch motion (a specific touch + * changed position) */ +#define EVT_ID_LEAVE_POINT 0x33 /* /< Touch leave the sensing area */ +#define EVT_ID_STATUS_UPDATE 0x43 /* /< FW report a system condition + * change */ +#define EVT_ID_USER_REPORT 0x53 /* /< User related events triggered + * (keys, gestures, proximity etc) */ +#define EVT_ID_DEBUG 0xE3 /* /< Debug Info */ +#define EVT_ID_ERROR 0xF3 /* /< Error Event */ + +/* /< Max number of unique event IDs supported */ +#define NUM_EVT_ID (((EVT_ID_ERROR & 0xF0) >> 4) + 1) + +/** @}*/ + +/* STATUS TYPE */ +/** @defgroup status_type Status Event Types + * @ingroup events_group + * Types of EVT_ID_STATUS_UPDATE events + * @{ + */ +#define EVT_TYPE_STATUS_ECHO 0x01 /* /< Echo event, + * contain the first 5 bytes of + * the FW command sent */ +#define EVT_TYPE_STATUS_GPIO_CHAR_DET 0x02 /*/< Gpio Charger detected */ +#define EVT_TYPE_STATUS_FRAME_DROP 0x03 /* /< Some frame was skipped + * during the elaboration */ +#define EVT_TYPE_STATUS_FORCE_CAL 0x05 /* /< Force Calibration has + * triggered */ +#define EVT_TYPE_STATUS_WATER 0x06 /* /< Water Mode */ +#define EVT_TYPE_STATUS_SS_RAW_SAT 0x07 /* /< Self Sense data saturated */ +#define EVT_TYPE_STATUS_PRE_WAT_DET 0x08 /* /< Previous Water Detect* */ +#define EVT_TYPE_STATUS_NOISE 0x09 /* /< Noise Status* */ +#define EVT_TYPE_STATUS_STIMPAD 0x0A /* /< Stimpad Status* */ +#define EVT_TYPE_STATUS_NO_TOUCH 0x0B /* /< No Touch Status* */ +#define EVT_TYPE_STATUS_IDLE 0x0C /* /< Idle Status* */ +#define EVT_TYPE_STATUS_PALM_TOUCH 0x0D /* /< Palm Touch Status* */ +#define EVT_TYPE_STATUS_GRIP_TOUCH 0x0E /* /< Grip Touch Status* */ +#define EVT_TYPE_STATUS_GOLDEN_RAW_VAL 0x0F /* /< Golden Raw + * Validation Status */ +#define EVT_TYPE_STATUS_GOLDEN_RAW_ERR 0x16 /* /< Golden Raw + * Data Abnormal */ +#ifdef SUPPORT_PROX_PALM +#define EVT_TYPE_STATUS_PROX_PALM 0x18 /* /< Proximity: Palm**/ +#endif + +/** @} */ + +/* USER TYPE */ +/** @defgroup user_type User Event Types + * @ingroup events_group + * Types of EVT_ID_USER_REPORT events generated by thw FW + * @{ + */ +#define EVT_TYPE_USER_KEY 0x00 /* /< Keys pressed/relesed event report + * */ +#define EVT_TYPE_USER_PROXIMITY 0x01 /* /< Proximity detection event report + * */ +#define EVT_TYPE_USER_GESTURE 0x02 /* /< Gesture detection event report */ +/** @}*/ + +/* ERROR TYPE */ +/** @defgroup error_type Error Event Types + * @ingroup events_group + * Types of EVT_ID_ERROR events reported by the FW + * @{ + */ +#define EVT_TYPE_ERROR_HARD_FAULT 0x02 /* /< Hard Fault */ +#define EVT_TYPE_ERROR_WATCHDOG 0x06 /* /< Watchdog timer expired */ + +#define EVT_TYPE_ERROR_CRC_CFG_HEAD 0x20 /* /< CRC error in the Config + * Area Header */ +#define EVT_TYPE_ERROR_CRC_CFG 0x21 /* /< CRC error in the Config + * Area */ +#define EVT_TYPE_ERROR_CRC_PANEL_HEAD 0x22 /* /< CRC error in the Panel + * Area Header */ +#define EVT_TYPE_ERROR_CRC_PANEL 0x23 /* /< CRC error in the Panel + * Area */ + +#define EVT_TYPE_ERROR_ITO_FORCETOGND 0x60 /* /< Force channel/s short to + * ground */ +#define EVT_TYPE_ERROR_ITO_SENSETOGND 0x61 /* /< Sense channel/s short to + * ground */ +#define EVT_TYPE_ERROR_ITO_FORCETOVDD 0x62 /* /< Force channel/s short to + * VDD */ +#define EVT_TYPE_ERROR_ITO_SENSETOVDD 0x63 /* /< Sense channel/s short to + * VDD */ +#define EVT_TYPE_ERROR_ITO_FORCE_P2P 0x64 /* /< Pin to Pin short Force + * channel/s */ +#define EVT_TYPE_ERROR_ITO_SENSE_P2P 0x65 /* /< Pin to Pin short Sense + * channel/s */ +#define EVT_TYPE_ERROR_ITO_FORCEOPEN 0x66 /* /< Force Panel open */ +#define EVT_TYPE_ERROR_ITO_SENSEOPEN 0x67 /* /< Sense Panel open */ +#define EVT_TYPE_ERROR_ITO_KEYOPEN 0x68 /* /< Key open */ + +#define EVT_TYPE_ERROR_CRC_CX_HEAD 0xA0 /* /< CRC error in the CX Area + * Header */ +#define EVT_TYPE_ERROR_CRC_CX 0xA1 /* /< CRC error in the CX Area + * */ +#define EVT_TYPE_ERROR_FLASH_FAILED 0xA4 /* Flash error, cause unknown */ +#define EVT_TYPE_ERROR_CRC_CX_SUB_HEAD 0xA5 /* /< CRC error in the CX + * Subsection Area Header */ +#define EVT_TYPE_ERROR_CRC_CX_SUB 0xA6 /* /< CRC error in the CX + * Subsection Area */ + +#define EVT_TYPE_ERROR_ESD 0xF0 /* /< ESD error */ + +#define EVT_TYPE_ERROR_OSC_TRIM 0x24 /* /< OSC Trim error */ +#define EVT_TYPE_ERROR_AOFFSET_TRIM 0x29 /* /< Aoffset Trim error */ +/** @}*/ + + +/** @defgroup address Chip Address + * Collection of HW and SW Addresses useful to collect different kind of data + * @{ + */ + +/** @defgroup config_adr SW Address + * @ingroup address + * Important addresses of data stored into Config memory (and sometimes their + * dimensions) + * @{ + */ +#define ADDR_CONFIG_ID 0x0010 /* /< Starting Address of the config ID + * */ +#define CONFIG_ID_BYTE 2 /* /< Number of bytes of config ID */ +#define ADDR_CONFIG_SENSE_LEN 0x0030 /* /< Address where is stored the number + * of sense channels */ +#define ADDR_CONFIG_FORCE_LEN 0x0031 /* /< Address where is stored the number + * of force channels */ +#define ADDR_CONFIG_AUTOCAL 0x0040 /* /< Address where is stored the Auto + * Calibration register */ +#define ADDR_CONFIG_T_CYCLE 0x00ED /* /< Address where is stored the MS T + * cycle */ +#define ADDR_CONFIG_MNM 0x01F0 /* /< Address where is stored the MNM + * register */ +#define ADDR_CONFIG_MRN 0x01F1 /* /< Address where is stored the MRN + * register */ +#define ADDR_CONFIG_R0_CYCLE 0x01F2 /* /< Address where is stored the first + * R cycle */ +/** @}*/ + +/** @}*/ + +/* @defgroup mp_flags MP Flags value + * @ingroup mp_test + * Specify the MP flags value which are written into the flash after performing + * a full panel initialization which pass all the tests. + * @{ + */ + +#define MP_FLAG_FACTORY 0xA5 /* /< Full Panel Init done in factory */ +#define MP_FLAG_BOOT 0x5A /* /< Full Panel Init done at boot */ +#define MP_FLAG_OTHERS 0xFF /* /< Full Panel Init done somewhere + * else + */ + +/** @}*/ + +/** @}*/ + +/* ERROR INFO */ +#define ERROR_DUMP_ROW_SIZE 32 /* /< number of rows of the error memory + * */ +#define ERROR_DUMP_COL_SIZE 4 /* /< number of bytes for each row of + * the error memory */ +#define ERROR_DUMP_SIGNATURE 0xFA5005AF /* /< first row signature of a + * proper dump */ + +/* Touch Types */ +#define TOUCH_TYPE_INVALID 0x00 /* /< Invalid touch type */ +#define TOUCH_TYPE_FINGER 0x01 /* /< Finger touch */ +#define TOUCH_TYPE_GLOVE 0x02 /* /< Glove touch */ +#define TOUCH_TYPE_STYLUS 0x03 /* /< Stylus touch */ +#define TOUCH_TYPE_PALM 0x04 /* /< Palm touch */ +#define TOUCH_TYPE_HOVER 0x00 /* /< Hovering touch */ +#define TOUCH_TYPE_GRIP 0x07 /* /< Hovering touch */ + +/* Keys code */ +#define FTS_KEY_0 0x01 /* /< Key 0 bit */ +#define FTS_KEY_1 0x02 /* /< Key 1 bit */ +#define FTS_KEY_2 0x04 /* /< Key 2 bit */ +#define FTS_KEY_3 0x08 /* /< Key 3 bit */ +#define FTS_KEY_4 0x10 /* /< Key 4 bit */ +#define FTS_KEY_5 0x20 /* /< Key 5 bit */ +#define FTS_KEY_6 0x40 /* /< Key 6 bit */ +#define FTS_KEY_7 0x80 /* /< Key 7 bit */ + +#endif diff --git a/fts_lib/ftsTest.c b/fts_lib/ftsTest.c new file mode 100644 index 0000000..0e1a15f --- /dev/null +++ b/fts_lib/ftsTest.c @@ -0,0 +1,7770 @@ +/* + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS API for MP test * + * * + ************************************************************************** + ************************************************************************** + * + */ + +/*! + * \file ftsTest.c + * \brief Contains all the functions related to the Mass Production Test + */ + +#include "ftsCompensation.h" +#include "ftsCore.h" +#include "ftsError.h" +#include "ftsFrame.h" +#include "ftsHardware.h" +#include "ftsIO.h" +#include "ftsSoftware.h" +#include "ftsTest.h" +#include "ftsTime.h" +#include "ftsTool.h" +#include "../fts.h" + + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <stdarg.h> +#include <linux/serio.h> +#include <linux/time.h> +#include <linux/delay.h> +#include <linux/ctype.h> +#include <linux/firmware.h> + + +#ifdef LIMITS_H_FILE +#include "../fts_limits.h" +#endif + + +TestToDo tests; /* /< global variable that specify the tests to perform during + * the Mass Production Test */ +static LimitFile limit_file; /* /< variable which contains the limit file + * during test */ + +/** + * Initialize the testToDo variable with the default tests to perform during + * the Mass Production Test + * @return OK + */ +int initTestToDo(void) +{ + /*** Initialize Limit File ***/ + limit_file.size = 0; + limit_file.data = NULL; + strlcpy(limit_file.name, " ", MAX_LIMIT_FILE_NAME); + +#ifndef COMPUTE_INIT_METHOD + tests.MutualRawAdjITO = 1; + tests.MutualRawMapITO = 1; + + tests.MutualRaw = 0; + tests.MutualRawMap = 1; + tests.MutualRawGap = 0; + tests.MutualRawAdj = 0; + tests.MutualRawAdjGap = 0; + tests.MutualRawAdjPeak = 0; + + tests.MutualRawLP = 0; + tests.MutualRawGapLP = 0; + tests.MutualRawMapLP = 1; + tests.MutualRawAdjLP = 0; + + tests.MutualCx1 = 0; + tests.MutualCx2 = 0; + tests.MutualCx2Adj = 0; + tests.MutualCxTotal = 0; + tests.MutualCxTotalAdj = 0; + + tests.MutualCx1LP = 0; + tests.MutualCx2LP = 0; + tests.MutualCx2AdjLP = 0; + tests.MutualCxTotalLP = 0; + tests.MutualCxTotalAdjLP = 0; + +#ifdef PHONE_KEY + tests.MutualKeyRaw = 0; +#else + tests.MutualKeyRaw = 0; +#endif + tests.MutualKeyCx1 = 0; + tests.MutualKeyCx2 = 0; +#ifdef PHONE_KEY + tests.MutualKeyCxTotal = 0; +#else + tests.MutualKeyCxTotal = 0; +#endif + + tests.SelfForceRaw = 0; + tests.SelfForceRawGap = 0; + tests.SelfForceRawMap = 1; + + tests.SelfForceRawLP = 0; + tests.SelfForceRawGapLP = 0; + tests.SelfForceRawMapLP = 1; + + tests.SelfForceIx1 = 0; + tests.SelfForceIx2 = 0; + tests.SelfForceIx2Adj = 0; + tests.SelfForceIxTotal = 0; + tests.SelfForceIxTotalAdj = 0; + tests.SelfForceCx1 = 0; + tests.SelfForceCx2 = 0; + tests.SelfForceCx2Adj = 0; + tests.SelfForceCxTotal = 0; + tests.SelfForceCxTotalAdj = 0; + tests.SelfForceIx1LP = 0; + tests.SelfForceIx2LP = 0; + tests.SelfForceIx2AdjLP = 0; + tests.SelfForceIxTotalLP = 0; + tests.SelfForceIxTotalAdjLP = 0; + tests.SelfForceCx1LP = 0; + tests.SelfForceCx2LP = 0; + tests.SelfForceCx2AdjLP = 0; + tests.SelfForceCxTotalLP = 0; + tests.SelfForceCxTotalAdjLP = 0; + + tests.SelfSenseRaw = 0; + tests.SelfSenseRawGap = 0; + tests.SelfSenseRawMap = 1; + + tests.SelfSenseRawLP = 0; + tests.SelfSenseRawGapLP = 0; + tests.SelfSenseRawMapLP = 1; + + tests.SelfSenseIx1 = 0; + tests.SelfSenseIx2 = 0; + tests.SelfSenseIx2Adj = 0; + tests.SelfSenseIxTotal = 0; + tests.SelfSenseIxTotalAdj = 0; + tests.SelfSenseCx1 = 0; + tests.SelfSenseCx2 = 0; + tests.SelfSenseCx2Adj = 0; + tests.SelfSenseCxTotal = 0; + tests.SelfSenseCxTotalAdj = 0; + tests.SelfSenseIx1LP = 0; + tests.SelfSenseIx2LP = 0; + tests.SelfSenseIx2AdjLP = 0; + tests.SelfSenseIxTotalLP = 0; + tests.SelfSenseIxTotalAdjLP = 0; + tests.SelfSenseCx1LP = 0; + tests.SelfSenseCx2LP = 0; + tests.SelfSenseCx2AdjLP = 0; + tests.SelfSenseCxTotalLP = 0; + tests.SelfSenseCxTotalAdjLP = 0; +#else + tests.MutualRawAdjITO = 1; + tests.MutualRawMapITO = 1; + + tests.MutualRaw = 1; /* in case of YOCTA please use Map */ + tests.MutualRawMap = 0; + tests.MutualRawGap = 0; + tests.MutualRawAdj = 0; + tests.MutualRawAdjGap = 0; + tests.MutualRawAdjPeak = 0; + + tests.MutualRawLP = 0; /* in case of YOCTA please use Map */ + tests.MutualRawGapLP = 0; + tests.MutualRawMapLP = 0; + tests.MutualRawAdjLP = 0; + + tests.MutualCx1 = 0; + tests.MutualCx2 = 0; + tests.MutualCx2Adj = 0; + tests.MutualCxTotal = 0; + tests.MutualCxTotalAdj = 0; + + tests.MutualCx1LP = 0; + tests.MutualCx2LP = 1; + tests.MutualCx2AdjLP = 1; + tests.MutualCxTotalLP = 0; + tests.MutualCxTotalAdjLP = 0; + +#ifdef PHONE_KEY + tests.MutualKeyRaw = 0; +#else + tests.MutualKeyRaw = 0; +#endif + tests.MutualKeyCx1 = 0; + tests.MutualKeyCx2 = 0; +#ifdef PHONE_KEY + tests.MutualKeyCxTotal = 0; +#else + tests.MutualKeyCxTotal = 0; +#endif + + tests.SelfForceRaw = 1; /* in case of YOCTA please use Map */ + tests.SelfForceRawGap = 0; + tests.SelfForceRawMap = 0; + + tests.SelfForceRawLP = 1; /* in case of YOCTA please use Map */ + tests.SelfForceRawGapLP = 0; + tests.SelfForceRawMapLP = 0; + + tests.SelfForceIx1 = 0; + tests.SelfForceIx2 = 0; + tests.SelfForceIx2Adj = 0; + tests.SelfForceIxTotal = 1; + tests.SelfForceIxTotalAdj = 0; + tests.SelfForceCx1 = 0; + tests.SelfForceCx2 = 0; + tests.SelfForceCx2Adj = 0; + tests.SelfForceCxTotal = 0; + tests.SelfForceCxTotalAdj = 0; + tests.SelfForceIx1LP = 0; + tests.SelfForceIx2LP = 0; + tests.SelfForceIx2AdjLP = 0; + tests.SelfForceIxTotalLP = 1; + tests.SelfForceIxTotalAdjLP = 0; + tests.SelfForceCx1LP = 0; + tests.SelfForceCx2LP = 0; + tests.SelfForceCx2AdjLP = 0; + tests.SelfForceCxTotalLP = 0; + tests.SelfForceCxTotalAdjLP = 0; + + tests.SelfSenseRaw = 1; + tests.SelfSenseRawGap = 0; + tests.SelfSenseRawMap = 0; + + tests.SelfSenseRawLP = 0; + tests.SelfSenseRawGapLP = 0; + tests.SelfSenseRawMapLP = 0; + + tests.SelfSenseIx1 = 0; + tests.SelfSenseIx2 = 0; + tests.SelfSenseIx2Adj = 0; + tests.SelfSenseIxTotal = 1; + tests.SelfSenseIxTotalAdj = 0; + tests.SelfSenseCx1 = 0; + tests.SelfSenseCx2 = 0; + tests.SelfSenseCx2Adj = 0; + tests.SelfSenseCxTotal = 0; + tests.SelfSenseCxTotalAdj = 0; + tests.SelfSenseIx1LP = 0; + tests.SelfSenseIx2LP = 0; + tests.SelfSenseIx2AdjLP = 0; + tests.SelfSenseIxTotalLP = 0; + tests.SelfSenseIxTotalAdjLP = 0; + tests.SelfSenseCx1LP = 0; + tests.SelfSenseCx2LP = 0; + tests.SelfSenseCx2AdjLP = 0; + tests.SelfSenseCxTotalLP = 0; + tests.SelfSenseCxTotalAdjLP = 0; +#endif + return OK; +} + +/** + * Compute the Horizontal adjacent matrix doing the abs of the difference + * between the column i with the i-1 one. \n + * Both the original data matrix and the adj matrix are disposed as 1 dimension + * array one row after the other \n + * The resulting matrix has one column less than the starting original one \n + * @param data pointer to the array of signed bytes containing the original + * data + * @param row number of rows of the original data + * @param column number of columns of the original data + * @param result pointer of a pointer to an array of unsigned bytes which will + * contain the adj matrix + * @return OK if success or an error code which specify the type of error + */ +int computeAdjHoriz(i8 *data, int row, int column, u8 **result) +{ + int i, j; + int size = row * (column - 1); + + if (column < 2) { + pr_err("computeAdjHoriz: ERROR %08X\n", ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + *result = (u8 *)kmalloc(size * sizeof(u8), GFP_KERNEL); + if (*result == NULL) { + pr_err("computeAdjHoriz: ERROR %08X\n", ERROR_ALLOC); + return ERROR_ALLOC; + } + + for (i = 0; i < row; i++) + for (j = 1; j < column; j++) + *(*result + (i * (column - 1) + (j - 1))) = + abs(data[i * column + j] - data[i * column + + (j - 1)]); + + return OK; +} + +/** + * Compute the Horizontal adjacent matrix of short values doing the abs of + * the difference between the column i with the i-1 one. + * Both the original data matrix and the adj matrix are disposed as 1 dimension + * array one row after the other \n + * The resulting matrix has one column less than the starting original one \n + * @param data pointer to the array of signed bytes containing the original + * data + * @param row number of rows of the original data + * @param column number of columns of the original data + * @param result pointer of a pointer to an array of unsigned bytes which + * will contain the adj matrix + * @return OK if success or an error code which specify the type of error + */ +int computeAdjHorizTotal(short *data, int row, int column, u16 **result) +{ + int i, j; + int size = row * (column - 1); + + if (column < 2) { + pr_err("computeAdjHorizTotal: ERROR %08X\n", + ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + *result = (u16 *)kmalloc(size * sizeof(u16), GFP_KERNEL); + if (*result == NULL) { + pr_err("computeAdjHorizTotal: ERROR %08X\n", ERROR_ALLOC); + return ERROR_ALLOC; + } + + for (i = 0; i < row; i++) + for (j = 1; j < column; j++) + *(*result + (i * (column - 1) + (j - 1))) = + abs(data[i * column + j] - data[i * column + + (j - 1)]); + + return OK; +} + +/** + * Compute the Vertical adjacent matrix doing the abs of the difference between + * the row i with the i-1 one. + * Both the original data matrix and the adj matrix are disposed as 1 dimension + * array one row after the other. \n + * The resulting matrix has one column less than the starting original one \n + * @param data pointer to the array of signed bytes containing the original + * data + * @param row number of rows of the original data + * @param column number of columns of the original data + * @param result pointer of a pointer to an array of unsigned bytes which will + * contain the adj matrix + * @return OK if success or an error code which specify the type of error + */ +int computeAdjVert(i8 *data, int row, int column, u8 **result) +{ + int i, j; + int size = (row - 1) * (column); + + if (row < 2) { + pr_err("computeAdjVert: ERROR %08X\n", ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + *result = (u8 *)kmalloc(size * sizeof(u8), GFP_KERNEL); + if (*result == NULL) { + pr_err("computeAdjVert: ERROR %08X\n", ERROR_ALLOC); + return ERROR_ALLOC; + } + + for (i = 1; i < row; i++) + for (j = 0; j < column; j++) + *(*result + ((i - 1) * column + j)) = + abs(data[i * column + j] - data[(i - 1) * + column + j]); + + return OK; +} + +/** + * Compute the Vertical adjacent matrix of short values doing the abs of + * the difference between the row i with the i-1 one. + * Both the original data matrix and the adj matrix are disposed as 1 dimension + * array one row after the other. \n + * The resulting matrix has one column less than the starting original one \n + * @param data pointer to the array of signed bytes containing the original + * data + * @param row number of rows of the original data + * @param column number of columns of the original data + * @param result pointer of a pointer to an array of unsigned bytes which will + * contain the adj matrix + * @return OK if success or an error code which specify the type of error + */ +int computeAdjVertTotal(short *data, int row, int column, u16 **result) +{ + int i, j; + int size = (row - 1) * (column); + + if (row < 2) { + pr_err("computeAdjVertTotal: ERROR %08X\n", ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + *result = (u16 *)kmalloc(size * sizeof(u16), GFP_KERNEL); + if (*result == NULL) { + pr_err("computeAdjVertTotal: ERROR %08X\n", ERROR_ALLOC); + return ERROR_ALLOC; + } + + for (i = 1; i < row; i++) + for (j = 0; j < column; j++) + *(*result + ((i - 1) * column + j)) = + abs(data[i * column + j] - data[(i - 1) * + column + j]); + + return OK; +} + +/** + * Compute the Horizontal adjacent matrix doing the abs of the difference + * between the column i with the i-1 one. \n + * Both the original data matrix and the adj matrix are disposed as 1 dimension + * array one row after the other \n + * The resulting matrix has one column less than the starting original one \n + * @param data pointer to the array of unsigned bytes containing the original + * data + * @param row number of rows of the original data + * @param column number of columns of the original data + * @param result pointer of a pointer to an array of unsigned bytes which will + * contain the adj matrix + * @return OK if success or an error code which specify the type of error + */ +int computeAdjHorizFromU(u8 *data, int row, int column, u8 **result) +{ + int i, j; + int size = row * (column - 1); + + if (column < 2) { + pr_err("computeAdjHoriz: ERROR %08X\n", ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + *result = (u8 *)kmalloc(size * sizeof(u8), GFP_KERNEL); + if (*result == NULL) { + pr_err("computeAdjHoriz: ERROR %08X\n", ERROR_ALLOC); + return ERROR_ALLOC; + } + + for (i = 0; i < row; i++) + for (j = 1; j < column; j++) + *(*result + (i * (column - 1) + (j - 1))) = + abs(data[i * column + j] - data[i * column + + (j - 1)]); + + return OK; +} + +/** + * Compute the Horizontal adjacent matrix of u16 values doing the abs of + * the difference between the column i with the i-1 one. + * Both the original data matrix and the adj matrix are disposed as 1 dimension + * array one row after the other \n + * The resulting matrix has one column less than the starting original one \n + * @param data pointer to the array of unsigned bytes containing the original + * data + * @param row number of rows of the original data + * @param column number of columns of the original data + * @param result pointer of a pointer to an array of unsigned bytes which will + * contain the adj matrix + * @return OK if success or an error code which specify the type of error + */ +int computeAdjHorizTotalFromU(u16 *data, int row, int column, u16 **result) +{ + int i, j; + int size = row * (column - 1); + + if (column < 2) { + pr_err("computeAdjHorizTotal: ERROR %08X\n", + ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + *result = (u16 *)kmalloc(size * sizeof(u16), GFP_KERNEL); + if (*result == NULL) { + pr_err("computeAdjHorizTotal: ERROR %08X\n", ERROR_ALLOC); + return ERROR_ALLOC; + } + + for (i = 0; i < row; i++) + for (j = 1; j < column; j++) + *(*result + (i * (column - 1) + (j - 1))) = + abs(data[i * column + j] - data[i * column + + (j - 1)]); + + return OK; +} + +/** + * Compute the Vertical adjacent matrix doing the abs of the difference between + * the row i with the i-1 one. + * Both the original data matrix and the adj matrix are disposed as 1 dimension + * array one row after the other. \n + * The resulting matrix has one column less than the starting original one \n + * @param data pointer to the array of unsigned bytes containing the original + * data + * @param row number of rows of the original data + * @param column number of columns of the original data + * @param result pointer of a pointer to an array of unsigned bytes which will + * contain the adj matrix + * @return OK if success or an error code which specify the type of error + */ +int computeAdjVertFromU(u8 *data, int row, int column, u8 **result) +{ + int i, j; + int size = (row - 1) * (column); + + if (row < 2) { + pr_err("computeAdjVert: ERROR %08X\n", ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + *result = (u8 *)kmalloc(size * sizeof(u8), GFP_KERNEL); + if (*result == NULL) { + pr_err("computeAdjVert: ERROR %08X\n", ERROR_ALLOC); + return ERROR_ALLOC; + } + + for (i = 1; i < row; i++) + for (j = 0; j < column; j++) + *(*result + ((i - 1) * column + j)) = + abs(data[i * column + j] - data[(i - 1) * + column + j]); + + return OK; +} + +/** + * Compute the Vertical adjacent matrix of u16 values doing the abs of + * the difference between the row i with the i-1 one. + * Both the original data matrix and the adj matrix are disposed as 1 dimension + * array one row after the other. \n + * The resulting matrix has one column less than the starting original one \n + * @param data pointer to the array of unsigned bytes containing the original + * data + * @param row number of rows of the original data + * @param column number of columns of the original data + * @param result pointer of a pointer to an array of unsigned bytes which will + * contain the adj matrix + * @return OK if success or an error code which specify the type of error + */ +int computeAdjVertTotalFromU(u16 *data, int row, int column, u16 **result) +{ + int i, j; + int size = (row - 1) * (column); + + if (row < 2) { + pr_err("computeAdjVertTotal: ERROR %08X\n", ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + *result = (u16 *)kmalloc(size * sizeof(u16), GFP_KERNEL); + if (*result == NULL) { + pr_err("computeAdjVertTotal: ERROR %08X\n", ERROR_ALLOC); + return ERROR_ALLOC; + } + + for (i = 1; i < row; i++) + for (j = 0; j < column; j++) + *(*result + ((i - 1) * column + j)) = + abs(data[i * column + j] - data[(i - 1) * + column + j]); + + return OK; +} + + + +/** + * Check that each value of a matrix of short doesn't exceed a min and a Max + * value + * (these values are included in the interval). \n + * The matrix is stored as 1 dimension array one row after the other. \n + * @param data pointer to the array of short containing the data to check + * @param row number of rows of data + * @param column number of columns of data + * @param min minimum value allowed + * @param max Maximum value allowed + * @return the number of elements that overcome the specified interval (0 = OK) + */ +int checkLimitsMinMax(short *data, int row, int column, int min, int max) +{ + int i, j; + int count = 0; + + for (i = 0; i < row; i++) { + for (j = 0; j < column; j++) { + if (data[i * column + j] < min || data[i * column + j] > + max) { + pr_info("%s: Node[%d,%d] = %d exceed limit [%d, %d]\n", + __func__, i, j, data[i * column + j], min, max); + count++; + } + } + } + + return count; /* if count is 0 = OK, test completed successfully */ +} + +/** + * Check that the difference between the max and min of a matrix of short + * is less or equal to a threshold.\n + * The matrix is stored as 1 dimension array one row after the other. + * @param data pointer to the array of short containing the data to check + * @param row number of rows of data + * @param column number of columns of data + * @param threshold threshold value allowed + * @return OK if the difference is <= to threshold otherwise + * ERROR_TEST_CHECK_FAIL + */ +int checkLimitsGap(short *data, int row, int column, int threshold) +{ + int i, j; + int min_node; + int max_node; + + if (row == 0 || column == 0) { + pr_err("checkLimitsGap: invalid number of rows = %d or columns = %d ERROR %08X\n", + row, column, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + min_node = data[0]; + max_node = data[0]; + + for (i = 0; i < row; i++) { + for (j = 0; j < column; j++) { + if (data[i * column + j] < min_node) + min_node = data[i * column + j]; + else if (data[i * column + j] > max_node) + max_node = data[i * column + j]; + } + } + + if (max_node - min_node > threshold) { + pr_err("checkLimitsGap: GAP = %d exceed limit %d\n", + max_node - min_node, threshold); + return ERROR_TEST_CHECK_FAIL; + } else + return OK; +} + +/* + * Check that the difference between the max and min of a matrix of short is + * less or equal to a threshold.\n + * The matrix is stored as 1 dimension array one row after the other. + * @param data pointer to the array of short containing the data to check + * @param row number of rows of data + * @param column number of columns of data + * @param threshold threshold value allowed + * @param row_start index of the starting column which should be considered + * @param column_start index of the starting column which should be considered + * @param row_end number of index to subtract to row to identify last + * valid row to check + * @param column_end number of index to subtract to column to identify last + * valid column to check + * @return OK if the difference is <= to threshold otherwise + * ERROR_TEST_CHECK_FAIL + */ +int checkLimitsGapOffsets(short *data, int row, int column, int threshold, + int row_start, int column_start, int row_end, int column_end) +{ + int i, j; + int min_node; + int max_node; + + if (row == 0 || column == 0) { + pr_err("checkLimitsGap: invalid number of rows = %d or columns = %d ERROR %08X\n", + row, column, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + min_node = data[row_start*column+column_start]; + max_node = data[row_start*column+column_start]; + + for (i = row_start; i < row-row_end; i++) { + for (j = column_start; j < column-column_end; j++) { + if (data[i * column + j] < min_node) + min_node = data[i * column + j]; + else if (data[i * column + j] > max_node) + max_node = data[i * column + j]; + } + } + + if (max_node - min_node > threshold) { + pr_err("checkLimitsGap: GAP = %d exceed limit %d\n", + max_node - min_node, threshold); + return ERROR_TEST_CHECK_FAIL; + } else + return OK; +} + +/** + * Check that each value of a matrix of i8 doesn't exceed a specific min and + * Max value set for each node (these values are included in the interval). \n + * The matrixes of data, min and max values are stored as 1 dimension arrays + * one row after the other. + * @param data pointer to the array of short containing the data to check + * @param row number of rows of data + * @param column number of columns of data + * @param min pointer to a matrix which specify the minimum value allowed for + * each node + * @param max pointer to a matrix which specify the Maximum value allowed for + * each node + * @return the number of elements that overcome the specified interval (0 = OK) + */ +int checkLimitsMap(i8 *data, int row, int column, int *min, int *max) +{ + int i, j; + int count = 0; + + for (i = 0; i < row; i++) { + for (j = 0; j < column; j++) { + if (data[i * column + j] < min[i * column + j] || + data[i * column + j] > max[i * column + j]) { + pr_info("%s: Node[%d,%d] = %d exceed limit [%d, %d]\n", + __func__, i, j, data[i * column + j], + min[i * column + j], + max[i * column + j]); + count++; + } + } + } + + return count; /* if count is 0 = OK, test completed successfully */ +} + +/** + * Check that each value of a matrix of short doesn't exceed a specific min and + * Max value set for each node (these values are included in the interval). + * The matrixes of data, min and max values are stored as 1 dimension arrays + * one row after the other. + * @param data pointer to the array of short containing the data to check + * @param row number of rows of data + * @param column number of columns of data + * @param min pointer to a matrix which specify the minimum value allowed for + * each node + * @param max pointer to a matrix which specify the Maximum value allowed for + * each node + * @return the number of elements that overcome the specified interval (0 = OK) + */ +int checkLimitsMapTotal(short *data, int row, int column, int *min, int *max) +{ + int i, j; + int count = 0; + + for (i = 0; i < row; i++) { + for (j = 0; j < column; j++) { + if (data[i * column + j] < min[i * column + j] || + data[i * column + j] > max[i * column + j]) { + pr_info("%s: Node[%d,%d] = %d exceed limit [%d, %d]\n", + __func__, i, j, data[i * column + j], + min[i * column + j], + max[i * column + j]); + count++; + } + } + } + + return count; /* if count is 0 = OK, test completed successfully */ +} + +/** + * Check that each value of a matrix of u8 doesn't exceed a specific min and + * Max value set for each node (these values are included in the interval). \n + * The matrixes of data, min and max values are stored as 1 dimension arrays + * one row after the other. + * @param data pointer to the array of short containing the data to check + * @param row number of rows of data + * @param column number of columns of data + * @param min pointer to a matrix which specify the minimum value allowed for + * each node + * @param max pointer to a matrix which specify the Maximum value allowed for + * each node + * @return the number of elements that overcome the specified interval (0 = OK) + */ +int checkLimitsMapFromU(u8 *data, int row, int column, int *min, int *max) +{ + int i, j; + int count = 0; + + for (i = 0; i < row; i++) { + for (j = 0; j < column; j++) { + if (data[i * column + j] < min[i * column + j] || + data[i * column + j] > max[i * column + j]) { + pr_info("%s: Node[%d,%d] = %d exceed limit [%d, %d]\n", + __func__, i, j, data[i * column + j], + min[i * column + j], + max[i * column + j]); + count++; + } + } + } + + return count; /* if count is 0 = OK, test completed successfully */ +} + +/** + * Check that each value of a matrix of u16 doesn't exceed a specific min and + * Max value set for each node (these values are included in the interval). + * The matrixes of data, min and max values are stored as 1 dimension arrays + * one row after the other. + * @param data pointer to the array of short containing the data to check + * @param row number of rows of data + * @param column number of columns of data + * @param min pointer to a matrix which specify the minimum value allowed for + * each node + * @param max pointer to a matrix which specify the Maximum value allowed for + * each node + * @return the number of elements that overcome the specified interval (0 = OK) + */ +int checkLimitsMapTotalFromU(u16 *data, int row, int column, int *min, int *max) +{ + int i, j; + int count = 0; + + for (i = 0; i < row; i++) { + for (j = 0; j < column; j++) { + if (data[i * column + j] < min[i * column + j] || + data[i * column + j] > max[i * column + j]) { + pr_info("%s: Node[%d,%d] = %d exceed limit [%d, %d]\n", + __func__, i, j, data[i * column + j], + min[i * column + j], + max[i * column + j]); + count++; + } + } + } + + return count; /* if count is 0 = OK, test completed successfully */ +} + +/** + * Check that each value of a matrix of u8 doesn't exceed a specific Max value + * set for each node (max value is included in the interval). + * The matrixes of data and max values are stored as 1 dimension arrays one row + * after the other. + * @param data pointer to the array of short containing the data to check + * @param row number of rows of data + * @param column number of columns of data + * @param max pointer to a matrix which specify the Maximum value allowed for + * each node + * @return the number of elements that overcome the specified interval (0 = OK) + */ +int checkLimitsMapAdj(u8 *data, int row, int column, int *max) +{ + int i, j; + int count = 0; + + for (i = 0; i < row; i++) { + for (j = 0; j < column; j++) { + if (data[i * column + j] > max[i * column + j]) { + pr_info("%s: Node[%d,%d] = %d exceed limit > %d\n", + __func__, i, j, + data[i * column + j], + max[i * column + j]); + count++; + } + } + } + + return count; /* if count is 0 = OK, test completed successfully */ +} + +/** + * Check that each value of a matrix of u16 doesn't exceed a specific Max value + * set for each node (max value is included in the interval). + * The matrixes of data and max values are stored as 1 dimension arrays one row + * after the other. + * @param data pointer to the array of short containing the data to check + * @param row number of rows of data + * @param column number of columns of data + * @param max pointer to a matrix which specify the Maximum value allowed for + * each node + * @return the number of elements that overcome the specified interval (0 = OK) + */ +int checkLimitsMapAdjTotal(u16 *data, int row, int column, int *max) +{ + int i, j; + int count = 0; + + for (i = 0; i < row; i++) { + for (j = 0; j < column; j++) { + if (data[i * column + j] > max[i * column + j]) { + pr_info("%s: Node[%d,%d] = %d exceed limit > %d\n", + __func__, i, j, + data[i * column + j], + max[i * column + j]); + count++; + } + } + } + + return count; /* if count is 0 = OK, test completed successfully */ +} + +/** + * Perform an ITO test setting all the possible options + * (see @link ito_opt ITO Options @endlink) and checking MS Raw ADJ if enabled + * @param path_limits name of Production Limit file to load or + * "NULL" if the limits data should be loaded by a .h file + * @param todo pointer to a TestToDo variable which select the test to do + * @param frame pointer to a MutualSenseframe variable which will store the ITO + * MS RAW data. If NULL, no data will be returned while if MutualRawAdjITO + * item in todo is disabled the variable will be untouched + * @return OK if success or an error code which specify the type of error + */ +int production_test_ito(const char *path_limits, TestToDo *todo, + MutualSenseFrame *frame) +{ + int res = OK; + u8 sett[2] = { 0x00, 0x00 }; + MutualSenseFrame msRawFrame; + MutualSenseFrame *ptr_frame = NULL; + int *thresholds = NULL; + int *thresholds_min = NULL; + int *thresholds_max = NULL; + u16 *adj = NULL; + int trows, tcolumns; + + msRawFrame.node_data = NULL; + + pr_info("ITO Production test is starting...\n"); + + res = fts_system_reset(); + if (res < 0) { + pr_err("%s: ERROR %08X\n", __func__, ERROR_PROD_TEST_ITO); + return res | ERROR_PROD_TEST_ITO; + } + + sett[0] = SPECIAL_TUNING_IOFF; + pr_info("Trimming Ioff...\n"); + res = writeSysCmd(SYS_CMD_SPECIAL_TUNING, sett, 2); + if (res < OK) { + pr_err("production_test_ito: Trimm Ioff ERROR %08X\n", + (res | ERROR_PROD_TEST_ITO)); + return res | ERROR_PROD_TEST_ITO; + } + + sett[0] = 0xFF; + sett[1] = 0x01; + pr_info("ITO Check command sent...\n"); + res = writeSysCmd(SYS_CMD_ITO, sett, 2); + if (res < OK) { + pr_err("production_test_ito: ERROR %08X\n", + (res | ERROR_PROD_TEST_ITO)); + return res | ERROR_PROD_TEST_ITO; + } + + pr_info("ITO Command = OK!\n"); + + pr_info("MS RAW ITO ADJ TEST:\n"); + if (todo->MutualRawAdjITO == 1 || todo->MutualRawMapITO == 1) { + pr_info("Collecting MS Raw data...\n"); + + if (frame != NULL) { + pr_info("%s: Copying MS Raw data to caller!\n", + __func__); + ptr_frame = frame; + } else + ptr_frame = &msRawFrame; + + res |= getMSFrame3(MS_RAW, ptr_frame); + if (res < OK) { + pr_err("%s: getMSFrame failed... ERROR %08X\n", + __func__, ERROR_PROD_TEST_ITO); + goto ERROR; + } + + print_frame_short("MS Raw ITO frame =", + array1dTo2d_short( + (*ptr_frame).node_data, + (*ptr_frame).node_data_size, + (*ptr_frame).header.sense_node), + (*ptr_frame).header.force_node, + (*ptr_frame).header.sense_node); + + pr_info("MS RAW ITO ADJ HORIZONTAL TEST:\n"); + res = computeAdjHorizTotal((*ptr_frame).node_data, + (*ptr_frame).header.force_node, + (*ptr_frame).header.sense_node, + &adj); + if (res < OK) { + pr_err("%s: computeAdjHoriz failed... ERROR %08X\n", + __func__, ERROR_PROD_TEST_ITO); + goto ERROR; + } + + res = parseProductionTestLimits(path_limits, &limit_file, + MS_RAW_ITO_ADJH, &thresholds, + &trows, &tcolumns); + if (res < OK || (trows != (*ptr_frame).header.force_node || + tcolumns != (*ptr_frame).header.sense_node - + 1)) { + pr_err("%s: parseProductionTestLimits MS_RAW_ITO_ADJH failed... ERROR %08X\n", + __func__, ERROR_PROD_TEST_DATA); + goto ERROR; + } + + + res = checkLimitsMapAdjTotal(adj, + (*ptr_frame).header.force_node, + (*ptr_frame).header.sense_node - 1, + thresholds); + if (res != OK) { + pr_err("production_test_data: checkLimitsAdj MS RAW ITO ADJH failed... ERROR COUNT = %d\n", + res); + pr_err("MS RAW ITO ADJ HORIZONTAL TEST:.................FAIL\n\n"); + res = ERROR_PROD_TEST_ITO; + goto ERROR; + } else + pr_info("MS RAW ITO ADJ HORIZONTAL TEST:.................OK\n"); + + kfree(thresholds); + thresholds = NULL; + + kfree(adj); + adj = NULL; + + pr_info("MS RAW ITO ADJ VERTICAL TEST:\n"); + res = computeAdjVertTotal((*ptr_frame).node_data, + (*ptr_frame).header.force_node, + (*ptr_frame).header.sense_node, + &adj); + if (res < OK) { + pr_err("%s: computeAdjVert failed... ERROR %08X\n", + __func__, ERROR_PROD_TEST_ITO); + goto ERROR; + } + + res = parseProductionTestLimits(path_limits, &limit_file, + MS_RAW_ITO_ADJV, &thresholds, + &trows, &tcolumns); + if (res < OK || (trows != (*ptr_frame).header.force_node - 1 || + tcolumns != (*ptr_frame).header.sense_node)) { + pr_err("%s: parseProductionTestLimits MS_RAW_ITO_ADJV failed... ERROR %08X\n", + __func__, ERROR_PROD_TEST_ITO); + goto ERROR; + } + + + res = checkLimitsMapAdjTotal(adj, (*ptr_frame).header.force_node + - 1, (*ptr_frame).header.sense_node, + thresholds); + if (res != OK) { + pr_err("%s: checkLimitsAdj MS RAW ITO ADJV failed... ERROR COUNT = %d\n", + __func__, res); + pr_err("MS RAW ITO ADJ VERTICAL TEST:.................FAIL\n\n"); + res = ERROR_PROD_TEST_ITO; + goto ERROR; + } else + pr_info("MS RAW ITO ADJ VERTICAL TEST:.................OK\n"); + + kfree(thresholds); + thresholds = NULL; + + kfree(adj); + adj = NULL; + + pr_info("MS RAW ITO MIN MAX TEST:\n"); + if (todo->MutualRawMapITO == 1) { + res = parseProductionTestLimits(path_limits, + &limit_file, MS_RAW_ITO_EACH_NODE_MIN, + &thresholds_min, &trows, &tcolumns); + if (res < OK || (trows != + (*ptr_frame).header.force_node || + tcolumns != (*ptr_frame).header.sense_node)) { + pr_err("production_test_data: parseProductionMS_RAW_ITO_EACH_NODE_MIN failed..." + "ERROR %08X\n", ERROR_PROD_TEST_DATA); + res |= ERROR_PROD_TEST_DATA; + goto ERROR; + } + + res = parseProductionTestLimits(path_limits, + &limit_file, MS_RAW_ITO_EACH_NODE_MAX, + &thresholds_max, &trows, &tcolumns); + if (res < OK || (trows != + (*ptr_frame).header.force_node || + tcolumns != (*ptr_frame).header.sense_node)) { + pr_err("production_test_data: parseProductionMS_RAW_ITO_EACH_NODE_MAX failed..." + "ERROR %08X\n", ERROR_PROD_TEST_DATA); + res |= ERROR_PROD_TEST_DATA; + goto ERROR; + } + + res = checkLimitsMapTotal((*ptr_frame).node_data, + (*ptr_frame).header.force_node, + (*ptr_frame).header.sense_node, thresholds_min, + thresholds_max); + if (res != OK) { + pr_err("production_test_data: checkLimitsMinMax" + " MS RAW ITO failed... ERROR COUNT = %d\n", + res); + pr_err("MS RAW ITO MIN MAX TEST:.................FAIL\n\n"); + res |= ERROR_PROD_TEST_DATA; + goto ERROR; + } else { + pr_info("MS RAW ITO MIN MAX TEST:................OK\n"); + } + } else { + pr_info("MS RAW ITO MIN MAX TEST:.................SKIPPED\n"); + } + } else { + pr_info("MS RAW ITO TEST:.................SKIPPED\n"); + } + +ERROR: + kfree(thresholds); + kfree(adj); + kfree(msRawFrame.node_data); + kfree(thresholds_min); + kfree(thresholds_max); + freeLimitsFile(&limit_file); + res |= fts_system_reset(); + if (res < OK) { + pr_err("production_test_ito: ERROR %08X\n", + ERROR_PROD_TEST_ITO); + res = (res | ERROR_PROD_TEST_ITO); + } + return res; +} + +/** + * Perform the Initialization of the IC + * @param type type of initialization to do + * (see @link sys_special_opt Initialization Options (Full or Panel) @endlink) + * @return OK if success or an error code which specify the type of error + */ +int production_test_initialization(u8 type) +{ + int res; + + pr_info("INITIALIZATION Production test is starting...\n"); + if (type != SPECIAL_PANEL_INIT && type != SPECIAL_FULL_PANEL_INIT) { + pr_err("production_test_initialization: Type incompatible! Type = %02X ERROR %08X\n", + type, ERROR_OP_NOT_ALLOW | + ERROR_PROD_TEST_INITIALIZATION); + return ERROR_OP_NOT_ALLOW | ERROR_PROD_TEST_INITIALIZATION; + } + + res = fts_system_reset(); + if (res < 0) { + pr_err("production_test_initialization: ERROR %08X\n", + ERROR_PROD_TEST_INITIALIZATION); + return res | ERROR_PROD_TEST_INITIALIZATION; + } + + pr_info("INITIALIZATION command sent... %02X\n", type); + res = writeSysCmd(SYS_CMD_SPECIAL, &type, 1); + if (res < OK) { + pr_err("production_test_initialization: ERROR %08X\n", + (res | ERROR_PROD_TEST_INITIALIZATION)); + return res | ERROR_PROD_TEST_INITIALIZATION; + } + + + pr_info("Refresh Sys Info...\n"); + res |= readSysInfo(1); /* need to update the chipInfo in order + * to refresh several versions */ + + if (res < 0) { + pr_err("production_test_initialization: read sys info ERROR %08X\n", + ERROR_PROD_TEST_INITIALIZATION); + res = (res | ERROR_PROD_TEST_INITIALIZATION); + } + + return res; +} + + +// @param signature value of the MP flag to save if the Mass Production Test succeed +/** + * Perform a FULL (ITO + INIT + DATA CHECK) Mass Production Test of the IC + * @param pathThresholds name of Production Limit file to load or + * "NULL" if the limits data should be loaded by a .h file + * @param stop_on_fail if 1, the test flow stops at the first data check + * failure + * otherwise it keeps going performing all the selected test + * @param saveInit if >0 (possible values: NO_INIT, SPECIAL_PANEL_INIT or + * SPECIAL_FULL_PANEL_INIT), + * the Initialization of the IC is executed otherwise it is skipped + * @param todo pointer to a TestToDo variable which select the test to do + * @param mpflag MP flag value to write in case of successful test + * @return OK if success or an error code which specify the type of error + */ +int production_test_main(const char *pathThresholds, int stop_on_fail, + int saveInit, TestToDo *todo, u8 mpflag) +{ + int res, ret; + + pr_info("MAIN Production test is starting...\n"); + +#ifndef SKIP_PRODUCTION_TEST + pr_info("ITO TEST:\n"); + res = production_test_ito(pathThresholds, todo, NULL); + if (res < 0) { + pr_err("Error during ITO TEST! ERROR %08X\n", res); + /* in case of ITO TEST failure is no sense keep going */ + goto END; + } else + pr_info("ITO TEST OK!\n"); +#endif + + pr_info("INITIALIZATION TEST :\n"); + if (saveInit != NO_INIT) { + res = production_test_initialization((u8)saveInit); + if (res < 0) { + pr_err("Error during INITIALIZATION TEST! ERROR %08X\n", + res); + if (stop_on_fail) + goto END; + } else + pr_info("INITIALIZATION TEST OK!\n"); + } else + pr_info("INITIALIZATION TEST :................. SKIPPED\n"); + + if (saveInit != NO_INIT) { + pr_info("Cleaning up...\n"); + ret = fts_system_reset(); + if (ret < 0) { + pr_err("production_test_main: system reset ERROR %08X\n", + ret); + res |= ret; + if (stop_on_fail) + goto END; + } + } + +#ifndef SKIP_PRODUCTION_TEST + pr_info("PRODUCTION DATA TEST:\n"); + ret = production_test_data(pathThresholds, stop_on_fail, todo); + if (ret < OK) + pr_err("Error during PRODUCTION DATA TEST! ERROR %08X\n", ret); + else + pr_info("PRODUCTION DATA TEST OK!\n"); +#endif + +#ifdef COMPUTE_INIT_METHOD + if (saveInit != NO_INIT) { + pr_info("%s: Clearing the FIFO events!!!\n", __func__); + ret = flushFIFO(); + if (ret < OK) + pr_err("%s: Error while Flushing the FIFO! ERROR %8X\n", + __func__, ret); + /* save the mp flag to desired value + * because data check OK + */ + ret = saveMpFlag(mpflag); + if (ret < OK) + pr_err("Error while saving MP FLAG! ERROR %08X\n", + ret); + else + pr_info("MP FLAG saving OK!\n"); + } +#endif + + res |= ret; + /* the OR is important because if the data test is OK but + * the init test fail, the main production test result should = FAIL */ + +END: + if (res < OK) { + pr_err("MAIN Production test finished.................FAILED\n"); + return res; + } else { + pr_info("MAIN Production test finished.................OK\n"); + return OK; + } +} + +/** + * Perform all the test selected in a TestTodo variable related to MS raw data + * (touch, keys etc..) + * @param path_limits name of Production Limit file to load or + * "NULL" if the limits data should be loaded by a .h file + * @param stop_on_fail if 1, the test flow stops at the first data check + * failure + * otherwise it keeps going performing all the selected test + * @param todo pointer to a TestToDo variable which select the test to do + * @return OK if success or an error code which specify the type of error + */ +int production_test_ms_raw(const char *path_limits, int stop_on_fail, + TestToDo *todo) +{ + int ret, count_fail = 0; + MutualSenseFrame msRawFrame; + + + int *thresholds = NULL; + int trows, tcolumns; + int *thresholds_min = NULL; + int *thresholds_max = NULL; + u16 *adj = NULL; + + int maxAdjH, maxAdjV; + int i, z; + + msRawFrame.node_data = NULL; + + /************** Mutual Sense Test *************/ + pr_info("MS RAW DATA TEST is starting...\n"); + if (todo->MutualRaw == 1 || todo->MutualRawGap == 1 || + todo->MutualRawAdj == 1 || todo->MutualRawMap == 1 || + todo->MutualRawAdjGap == 1 || todo->MutualRawAdjPeak == 1) { + ret = setScanMode(SCAN_MODE_LOCKED, LOCKED_ACTIVE); + msleep(WAIT_FOR_FRESH_FRAMES); + ret |= setScanMode(SCAN_MODE_ACTIVE, 0x00); + msleep(WAIT_AFTER_SENSEOFF); +#ifdef READ_FILTERED_RAW + ret |= getMSFrame3(MS_FILTER, &msRawFrame); +#else + ret |= getMSFrame3(MS_RAW, &msRawFrame); +#endif + if (ret < OK) { + pr_err("production_test_data: getMSFrame failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + return ret | ERROR_PROD_TEST_DATA; + } + + print_frame_short("MS Raw frame =", + array1dTo2d_short( + msRawFrame.node_data, + msRawFrame.node_data_size, + msRawFrame.header.sense_node), + msRawFrame.header.force_node, + msRawFrame.header.sense_node); + + pr_info("MS RAW MIN MAX TEST:\n"); + if (todo->MutualRaw == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, + MS_RAW_MIN_MAX, + &thresholds, &trows, + &tcolumns); + if (ret < OK || (trows != 1 || tcolumns != 2)) { + pr_err("production_test_data: parseProductionTestLimits MS_RAW_MIN_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + + ret = checkLimitsMinMax(msRawFrame.node_data, + msRawFrame.header.force_node, + msRawFrame.header.sense_node, + thresholds[0], + thresholds[1]); + if (ret != OK) { + pr_err("production_test_data: checkLimitsMinMax MS RAW failed... ERROR COUNT = %d\n", + ret); + pr_err("MS RAW MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail == 1) + goto ERROR; + } else + pr_info("MS RAW MIN MAX TEST:.................OK\n"); + kfree(thresholds); + thresholds = NULL; + } else + pr_info("MS RAW MIN MAX TEST:.................SKIPPED\n"); + + pr_info("MS RAW MAP MIN MAX TEST:\n"); + if (todo->MutualRawMap == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, MS_RAW_EACH_NODE_MIN, + &thresholds_min, &trows, &tcolumns); + if (ret < OK || (trows != + msRawFrame.header.force_node || + tcolumns != + msRawFrame.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits MS_RAW_EACH_NODE_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + ret = parseProductionTestLimits(path_limits, + &limit_file, MS_RAW_EACH_NODE_MAX, + &thresholds_max, &trows, &tcolumns); + if (ret < OK || (trows != + msRawFrame.header.force_node || + tcolumns != + msRawFrame.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits MS_RAW_EACH_NODE_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + ret = checkLimitsMapTotal(msRawFrame.node_data, + msRawFrame.header.force_node, + msRawFrame.header.sense_node, thresholds_min, + thresholds_max); + if (ret != OK) { + pr_err("production_test_data: checkLimitsMinMaxEachNodeData failed... ERROR COUNT = %d\n", + ret); + pr_err("MS RAW MAP MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail == 1) + goto ERROR; + } else{ + pr_info("MS RAW MAP MIN MAX TEST:.................OK\n"); + } + if (thresholds_min != NULL) { + kfree(thresholds_min); + thresholds_min = NULL; + } + if (thresholds_max != NULL) { + kfree(thresholds_max); + thresholds_max = NULL; + } + } else + pr_info("MS RAW MAP MIN MAX TEST:.................SKIPPED\n"); + + + pr_info("MS RAW GAP TEST:\n"); + if (todo->MutualRawGap == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, MS_RAW_GAP, + &thresholds, &trows, + &tcolumns); + if (ret < OK || (trows != 1 || tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits MS_RAW_GAP failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsGap(msRawFrame.node_data, + msRawFrame.header.force_node, + msRawFrame.header.sense_node, + thresholds[0]); + if (ret != OK) { + pr_err("production_test_data: checkLimitsGap MS RAW failed... ERROR = %08X\n", + ret); + count_fail += 1; + if (stop_on_fail == 1) + goto ERROR; + } else + pr_info("MS RAW GAP TEST:.................OK\n\n"); + kfree(thresholds); + thresholds = NULL; + } else + pr_info("MS RAW GAP TEST:.................SKIPPED\n"); + + pr_info("MS RAW ADJ TEST:\n"); + if ((todo->MutualRawAdj == 1) || (todo->MutualRawAdjGap == 1) || + (todo->MutualRawAdjPeak == 1)) { + pr_info("MS RAW ADJ HORIZONTAL TEST:\n"); + ret = computeAdjHorizTotal(msRawFrame.node_data, + msRawFrame.header.force_node, + msRawFrame.header.sense_node, + &adj); + if (ret < OK) { + pr_err("production_test_data: computeAdjHoriz failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + if (todo->MutualRawAdj) { + pr_err("MS RAW ADJ HORIZONTAL MIN/MAX:\n"); + + ret = parseProductionTestLimits(path_limits, + &limit_file, + MS_RAW_ADJH, + &thresholds, + &trows, + &tcolumns); + if (ret < OK || + (trows != msRawFrame.header.force_node || + tcolumns != + msRawFrame.header.sense_node - 1)) { + pr_err("production_test_data: parseProductionTestLimits MS_RAW_ADJH failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + + ret = checkLimitsMapAdjTotal(adj, + msRawFrame.header.force_node, + msRawFrame.header.sense_node - 1, + thresholds); + if (ret != OK) { + pr_err("production_test_data: checkLimitsAdj MS RAW ADJH failed... ERROR COUNT = %d\n", + ret); + pr_err("%s MS RAW ADJ HORIZONTAL MIN/MAX:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail == 1) + goto ERROR; + } else + pr_info("MS RAW ADJ HORIZONTAL MIN/MAX:.................OK\n"); + + if (thresholds != NULL) { + kfree(thresholds); + thresholds = NULL; + } + } + + if (todo->MutualRawAdjGap) { + pr_info("MS RAW ADJ HORIZONTAL GAP:\n"); + + ret = parseProductionTestLimits(path_limits, + &limit_file, + MS_RAW_ADJH_GAP, + &thresholds, &trows, + &tcolumns); + if (ret < OK || (trows != 1 || + tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits MS_RAW_ADJH failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + + ret = checkLimitsGapOffsets(adj, + msRawFrame.header. + force_node, + msRawFrame.header. + sense_node - 1, + thresholds[0], + 1, 1, 1, 1); + if (ret != OK) { + pr_err("production_test_data: checkLimitsAdj MS RAW ADJH GAP failed...\n"); + pr_err("MS RAW ADJ HORIZONTAL GAP:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail == 1) + goto ERROR; + } else + pr_info("MS RAW ADJ HORIZONTAL GAP:.................OK\n"); + + if (thresholds != NULL) { + kfree(thresholds); + thresholds = NULL; + } + } + + + if (todo->MutualRawAdjPeak) { + int force_node = msRawFrame.header.force_node; + int sense_node = msRawFrame.header.sense_node; + + pr_info("MS RAW ADJ Peak: Getting max ADJH\n"); + maxAdjH = abs(adj[force_node + 1]); + /* skip nodes on the edges */ + for (i = 1; i < force_node - 1; i++) { + for (z = 1; z < sense_node - 2; z++) { + maxAdjH = + max(maxAdjH, + abs(adj[(i * force_node) + z])); + } + } + } + + kfree(adj); + adj = NULL; + + pr_info("MS RAW ADJ VERTICAL TESTs:\n"); + ret = computeAdjVertTotal(msRawFrame.node_data, + msRawFrame.header.force_node, + msRawFrame.header.sense_node, + &adj); + if (ret < OK) { + pr_err("production_test_data: computeAdjVert failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + if (todo->MutualRawAdj) { + pr_info("MS RAW ADJ VERTICAL MIN/MAX:\n"); + ret = parseProductionTestLimits(path_limits, + &limit_file, + MS_RAW_ADJV, + &thresholds, + &trows, + &tcolumns); + if (ret < OK || + (trows != + msRawFrame.header.force_node - 1 || + tcolumns != + msRawFrame.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits MS_RAW_ADJV failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + + ret = checkLimitsMapAdjTotal(adj, + msRawFrame.header.force_node - 1, + msRawFrame.header.sense_node, + thresholds); + if (ret != OK) { + pr_err("production_test_data: checkLimitsAdj MS RAW ADJV failed... ERROR COUNT = %d\n", + ret); + pr_err("MS RAW ADJ VERTICAL MIN/MAX:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail == 1) + goto ERROR; + } else + pr_info("MS RAW ADJ VERTICAL MIN/MAX:.................OK\n"); + + if (thresholds != NULL) { + kfree(thresholds); + thresholds = NULL; + } + } + + if (todo->MutualRawAdjGap) { + pr_err("MS RAW ADJ VERTICAL GAP:\n"); + ret = parseProductionTestLimits(path_limits, + &limit_file, + MS_RAW_ADJV_GAP, + &thresholds, &trows, + &tcolumns); + if (ret < OK || (trows != 1 || + tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits MS_RAW_ADJV_GAP failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + + ret = checkLimitsGapOffsets(adj, + msRawFrame.header.force_node - 1, + msRawFrame.header.sense_node, + thresholds[0], 1, 1, 1, 1); + if (ret != OK) { + pr_err("production_test_data: checkLimitsAdj MS RAW ADJV GAP failed... ERROR COUNT = %d\n", + ret); + pr_err("MS RAW ADJ VERTICAL GAP:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail == 1) + goto ERROR; + } else + pr_info("MS RAW ADJ VERTICAL GAP:.................OK\n"); + + if (thresholds != NULL) { + kfree(thresholds); + thresholds = NULL; + } + } + + if (todo->MutualRawAdjPeak) { + int force_node = msRawFrame.header.force_node; + int sense_node = msRawFrame.header.sense_node; + + pr_info("MS RAW ADJ Peak: Getting max ADJV\n"); + maxAdjV = abs(adj[force_node + 1]); + + /* skip nodes on the edges */ + for (i = 1; i < (force_node - 2); i++) { + for (z = 1; z < sense_node - 1; z++) { + maxAdjV = (maxAdjV < + abs(adj[(i * + force_node) + z])) ? + abs(adj[(i * + force_node) + z]) : + maxAdjV; + } + } + + ret = parseProductionTestLimits(path_limits, + &limit_file, + MS_RAW_ADJ_PEAK, + &thresholds, &trows, + &tcolumns); + if (ret < OK || (trows != 1 || + tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits MS_RAW_ADJV_PEAK failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + pr_info("maxAdjH = %d maxAdjV = %d threshold = %d\n", + maxAdjH, maxAdjV, thresholds[0]); + + ret = OK; + if (maxAdjH > maxAdjV) { + if (maxAdjH > thresholds[0]) + ret = ERROR_PROD_TEST_DATA; + } else { + if (maxAdjV > thresholds[0]) + ret = ERROR_PROD_TEST_DATA; + } + + if (ret != OK) { + pr_err("production_test_data: checkLimitsAdj MS RAW ADJV GAP failed... ERROR COUNT = %d\n", + ret); + pr_err("MS RAW ADJ PEAK:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail == 1) + goto ERROR; + } else + pr_info("MS RAW ADJ PEAK:.................OK\n"); + + if (thresholds != NULL) { + kfree(thresholds); + thresholds = NULL; + } + + } + + kfree(adj); + adj = NULL; + } else + pr_info("MS RAW ADJ TEST:.................SKIPPED\n"); + } else + pr_info("MS RAW FRAME TEST:.................SKIPPED\n"); + + pr_info("MS KEY RAW TEST:\n"); + if (todo->MutualKeyRaw == 1) { + ret = production_test_ms_key_raw(path_limits); + if (ret < 0) { + pr_err("production_test_data: production_test_ms_key_raw failed... ERROR = %08X\n", + ret); + count_fail += 1; + if (count_fail == 1) { + pr_err("MS RAW DATA TEST:.................FAIL fails_count = %d\n\n", + count_fail); + goto ERROR_LIMITS; + } + } + } else + pr_info("MS KEY RAW TEST:.................SKIPPED\n"); + + ret = production_test_ms_raw_lp(path_limits, stop_on_fail, todo); + if (ret < 0) { + pr_err("production_test_data: production_test_ms_raw_lp failed... ERROR = %08X\n", + ret); + count_fail += 1; + if (count_fail == 1) { + pr_err("MS RAW DATA TEST:.................FAIL fails_count = %d\n\n", + count_fail); + goto ERROR_LIMITS; + } + } + +ERROR: + + if (count_fail == 0) { + if (msRawFrame.node_data != NULL) { + kfree(msRawFrame.node_data); + msRawFrame.node_data = NULL; + } + pr_info("MS RAW DATA TEST finished!.................OK\n"); + return OK; + } else { + if (msRawFrame.node_data != NULL) + kfree(msRawFrame.node_data); + if (thresholds != NULL) + kfree(thresholds); + if (adj != NULL) + kfree(adj); + if (thresholds_min != NULL) { + kfree(thresholds_min); + thresholds_min = NULL; + } + if (thresholds_max != NULL) { + kfree(thresholds_max); + thresholds_max = NULL; + } + pr_err("MS RAW DATA TEST:.................FAIL fails_count = %d\n\n", + count_fail); + return ERROR_PROD_TEST_DATA | ERROR_TEST_CHECK_FAIL; + } + + +ERROR_LIMITS: + if (msRawFrame.node_data != NULL) + kfree(msRawFrame.node_data); + if (thresholds != NULL) + kfree(thresholds); + return ret; +} + + +/** + * Perform all the test selected in a TestTodo variable related to MS low power + * raw data + * @param path_limits name of Production Limit file to load or + * "NULL" if the limits data should be loaded by a .h file + * @param stop_on_fail if 1, the test flow stops at the first data check + * failure + * otherwise it keeps going performing all the selected test + * @param todo pointer to a TestToDo variable which select the test to do + * @return OK if success or an error code which specify the type of error + */ +int production_test_ms_raw_lp(const char *path_limits, int stop_on_fail, + TestToDo *todo) +{ + int ret, count_fail = 0; + MutualSenseFrame msRawFrame; + + + int *thresholds = NULL; + int trows, tcolumns; + int *thresholds_min = NULL; + int *thresholds_max = NULL; + u16 *adj = NULL; + + msRawFrame.node_data = NULL; + + /************** Mutual Sense Test **************/ + pr_info("MS RAW LP DATA TEST:\n"); + if (todo->MutualRawLP == 1 || todo->MutualRawGapLP == 1 || + todo->MutualRawAdjLP == 1 || todo->MutualRawMapLP) { + ret = setScanMode(SCAN_MODE_LOCKED, LOCKED_LP_ACTIVE); + msleep(WAIT_FOR_FRESH_FRAMES); + ret |= setScanMode(SCAN_MODE_ACTIVE, 0x00); + msleep(WAIT_AFTER_SENSEOFF); +#ifdef READ_FILTERED_RAW + ret |= getMSFrame3(MS_FILTER, &msRawFrame); +#else + ret |= getMSFrame3(MS_RAW, &msRawFrame); +#endif + if (ret < 0) { + pr_err("production_test_data: getMSFrame failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + return ret | ERROR_PROD_TEST_DATA; + } + + print_frame_short("MS Raw LP frame =", + array1dTo2d_short( + msRawFrame.node_data, + msRawFrame.node_data_size, + msRawFrame.header.sense_node), + msRawFrame.header.force_node, + msRawFrame.header.sense_node); + + pr_info("MS RAW LP MIN MAX TEST:\n"); + if (todo->MutualRawLP == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, + MS_RAW_LP_MIN_MAX, + &thresholds, &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + pr_err("production_test_data: parseProductionTestLimits MS_RAW_LP_MIN_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + + ret = checkLimitsMinMax(msRawFrame.node_data, + msRawFrame.header.force_node, + msRawFrame.header.sense_node, + thresholds[0], + thresholds[1]); + if (ret != OK) { + pr_err("production_test_data: checkLimitsMinMax MS RAW LP failed... ERROR COUNT = %d\n", + ret); + pr_err("MS RAW LP MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail == 1) + goto ERROR; + } else + pr_info("MS RAW LP MIN MAX TEST:.................OK\n"); + kfree(thresholds); + thresholds = NULL; + } else + pr_info("MS RAW LP MIN MAX TEST:.................SKIPPED\n"); + + pr_info("MS RAW LP MAP MIN MAX TEST:\n"); + if (todo->MutualRawMapLP == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, MS_RAW_LP_EACH_NODE_MIN, + &thresholds_min, &trows, &tcolumns); + if (ret < OK || + (trows != msRawFrame.header.force_node || + tcolumns != msRawFrame.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits MS_RAW_LP_EACH_NODE_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + ret = parseProductionTestLimits(path_limits, + &limit_file, MS_RAW_LP_EACH_NODE_MAX, + &thresholds_max, &trows, &tcolumns); + if (ret < OK || + (trows != msRawFrame.header.force_node || + tcolumns != msRawFrame.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits MS_RAW_LP_EACH_NODE_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + ret = checkLimitsMapTotal(msRawFrame.node_data, + msRawFrame.header.force_node, + msRawFrame.header.sense_node, thresholds_min, + thresholds_max); + if (ret != OK) { + pr_err("production_test_data: checkLimitsMinMaxEachNodeData failed... ERROR COUNT = %d\n", + ret); + pr_err("MS RAW LP MAP MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail == 1) + goto ERROR; + } else{ + pr_info("MS RAW LP MAP MIN MAX TEST:.................OK\n"); + } + if (thresholds_min != NULL) { + kfree(thresholds_min); + thresholds_min = NULL; + } + if (thresholds_max != NULL) { + kfree(thresholds_max); + thresholds_max = NULL; + } + } else + pr_info("MS RAW LP MAP MIN MAX TEST:.................SKIPPED\n"); + + + pr_info("MS RAW LP GAP TEST:\n"); + if (todo->MutualRawGapLP == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, + MS_RAW_LP_GAP, + &thresholds, &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits MS_RAW_LP_GAP failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsGap(msRawFrame.node_data, + msRawFrame.header.force_node, + msRawFrame.header.sense_node, + thresholds[0]); + if (ret != OK) { + pr_err("production_test_data: checkLimitsGap MS RAW LP failed... ERROR = %08X\n", + ret); + count_fail += 1; + if (stop_on_fail == 1) + goto ERROR; + } else + pr_info("MS RAW LP GAP TEST:.................OK\n\n"); + kfree(thresholds); + thresholds = NULL; + } else + pr_info("MS RAW LP GAP TEST:.................SKIPPED\n"); + + pr_info("MS RAW LP ADJ TEST:\n"); + if (todo->MutualRawAdjLP == 1) { + pr_info("MS RAW LP ADJ HORIZONTAL TEST:\n"); + ret = computeAdjHorizTotal(msRawFrame.node_data, + msRawFrame.header.force_node, + msRawFrame.header.sense_node, + &adj); + if (ret < 0) { + pr_err("production_test_data: computeAdjHoriz failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = parseProductionTestLimits(path_limits, + &limit_file, + MS_RAW_LP_ADJH, + &thresholds, &trows, + &tcolumns); + if (ret < 0 || (trows != msRawFrame.header.force_node || + tcolumns != + msRawFrame.header.sense_node - 1)) { + pr_err("production_test_data: parseProductionTestLimits MS_RAW_LP_ADJH failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + + ret = checkLimitsMapAdjTotal(adj, + msRawFrame.header. + force_node, + msRawFrame.header. + sense_node - 1, + thresholds); + if (ret != OK) { + pr_err("production_test_data: checkLimitsAdj MS RAW LP ADJH failed... ERROR COUNT = %d\n", + ret); + pr_err("MS RAW LP ADJ HORIZONTAL TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail == 1) + goto ERROR; + } else + pr_info("MS RAW LP ADJ HORIZONTAL TEST:.................OK\n"); + + kfree(thresholds); + thresholds = NULL; + + kfree(adj); + adj = NULL; + + pr_info("MS RAW LP ADJ VERTICAL TEST:\n"); + ret = computeAdjVertTotal(msRawFrame.node_data, + msRawFrame.header.force_node, + msRawFrame.header.sense_node, + &adj); + if (ret < 0) { + pr_err("production_test_data: computeAdjVert failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = parseProductionTestLimits(path_limits, + &limit_file, + MS_RAW_LP_ADJV, + &thresholds, &trows, + &tcolumns); + if (ret < 0 || (trows != msRawFrame.header.force_node - + 1 || tcolumns != + msRawFrame.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits MS_RAW_ADJV failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + + ret = checkLimitsMapAdjTotal(adj, + msRawFrame.header. + force_node - 1, + msRawFrame.header. + sense_node, thresholds); + if (ret != OK) { + pr_err("production_test_data: checkLimitsAdj MS RAW ADJV failed... ERROR COUNT = %d\n", + ret); + pr_err("MS RAW LP ADJ VERTICAL TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail == 1) + goto ERROR; + } else + pr_info("MS RAW LP ADJ VERTICAL TEST:.................OK\n"); + kfree(thresholds); + thresholds = NULL; + + kfree(adj); + adj = NULL; + } else + pr_info("MS RAW LP ADJ TEST:.................SKIPPED\n"); + } else + pr_info("MS RAW LP FRAME TEST:.................SKIPPED\n"); + +ERROR: + if (count_fail == 0) { + if (msRawFrame.node_data != NULL) { + kfree(msRawFrame.node_data); + msRawFrame.node_data = NULL; + } + pr_info("MS RAW DATA TEST finished!.................OK\n"); + return OK; + } else { + if (msRawFrame.node_data != NULL) + kfree(msRawFrame.node_data); + if (thresholds != NULL) + kfree(thresholds); + if (adj != NULL) + kfree(adj); + if (thresholds_min != NULL) { + kfree(thresholds_min); + thresholds_min = NULL; + } + if (thresholds_max != NULL) { + kfree(thresholds_max); + thresholds_max = NULL; + } + pr_err("MS RAW LP DATA TEST:.................FAIL fails_count = %d\n\n", + count_fail); + return ERROR_PROD_TEST_DATA | ERROR_TEST_CHECK_FAIL; + } + + +ERROR_LIMITS: + if (msRawFrame.node_data != NULL) + kfree(msRawFrame.node_data); + if (thresholds != NULL) + kfree(thresholds); + return ret; +} + +/** + * Perform MS raw test for keys data + * @param path_limits name of Production Limit file to load or + * "NULL" if the limits data should be loaded by a .h file + * @return OK if success or an error code which specify the type of error + */ +int production_test_ms_key_raw(const char *path_limits) +{ + int ret; + MutualSenseFrame msRawFrame; + + int *thresholds = NULL; + int trows, tcolumns; + + /************** Mutual Sense Test **************/ + pr_info("MS KEY RAW DATA TEST is starting...\n"); + ret = setScanMode(SCAN_MODE_ACTIVE, 0xFF); + msleep(WAIT_FOR_FRESH_FRAMES); + ret |= setScanMode(SCAN_MODE_ACTIVE, 0x00); + msleep(WAIT_AFTER_SENSEOFF); + ret |= getMSFrame3(MS_KEY_RAW, &msRawFrame); + if (ret < 0) { + pr_err("production_test_data: getMSKeyFrame failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + return ret | ERROR_PROD_TEST_DATA; + } + + ret = parseProductionTestLimits(path_limits, &limit_file, + MS_KEY_RAW_MIN_MAX, &thresholds, &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + pr_err("production_test_data: parseProductionTestLimits MS_KEY_RAW_MIN_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMinMax(msRawFrame.node_data, + msRawFrame.header.force_node, + msRawFrame.header.sense_node, + thresholds[0], thresholds[1]); + if (ret != OK) { + pr_err("production_test_data: checkLimitsMinMax MS KEY RAW failed... ERROR COUNT = %d\n", + ret); + goto ERROR; + } else + pr_info("MS KEY RAW TEST:.................OK\n\n"); + + kfree(thresholds); + thresholds = NULL; + + kfree(msRawFrame.node_data); + msRawFrame.node_data = NULL; + return OK; + +ERROR: + print_frame_short("MS Key Raw frame =", array1dTo2d_short( + msRawFrame.node_data, + msRawFrame.node_data_size, + msRawFrame.header.sense_node), + msRawFrame.header.force_node, + msRawFrame.header.sense_node); + if (msRawFrame.node_data != NULL) + kfree(msRawFrame.node_data); + if (thresholds != NULL) + kfree(thresholds); + pr_err("MS KEY RAW TEST:.................FAIL\n\n"); + return ERROR_PROD_TEST_DATA | ERROR_TEST_CHECK_FAIL; + +ERROR_LIMITS: + if (msRawFrame.node_data != NULL) + kfree(msRawFrame.node_data); + if (thresholds != NULL) + kfree(thresholds); + return ret; +} + +/** + * Perform all the tests selected in a TestTodo variable related to MS Init + * data (touch, keys etc..) + * @param path_limits name of Production Limit file to load or + * "NULL" if the limits data should be loaded by a .h file + * @param stop_on_fail if 1, the test flow stops at the first data check + * failure + * otherwise it keeps going performing all the selected test + * @param todo pointer to a TestToDo variable which select the test to do + * @return OK if success or an error code which specify the type of error + */ +int production_test_ms_cx(const char *path_limits, int stop_on_fail, + TestToDo *todo) +{ + int ret; + int count_fail = 0; + + int *thresholds = NULL; + int *thresholds_min = NULL; + int *thresholds_max = NULL; + int trows, tcolumns; + + MutualSenseData msCompData; + TotMutualSenseData totCompData; + + u8 *adjhor = NULL; + + u8 *adjvert = NULL; + + u16 container; + /* u16 *total_cx = NULL; */ + u16 *total_adjhor = NULL; + u16 *total_adjvert = NULL; + + + /* MS CX TEST */ + pr_info("MS CX Testes are starting...\n"); + + ret = readMutualSenseCompensationData(LOAD_CX_MS_TOUCH, &msCompData); + /* read MS compensation data */ + if (ret < 0) { + pr_err("production_test_data: readMutualSenseCompensationData failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + return ret | ERROR_PROD_TEST_DATA; + } + + ret = readTotMutualSenseCompensationData(LOAD_PANEL_CX_TOT_MS_TOUCH, + &totCompData); + /* read TOT MS compensation data */ + if (ret < 0) { + pr_err("production_test_data: readTotMutualSenseCompensationData failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + kfree(msCompData.node_data); + msCompData.node_data = NULL; + return ret | ERROR_PROD_TEST_DATA; + } + + pr_info("MS CX1 TEST:\n"); + if (todo->MutualCx1 == 1) { + ret = parseProductionTestLimits(path_limits, &limit_file, + MS_CX1_MIN_MAX, &thresholds, + &trows, &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + pr_err("production_test_data: parseProductionTestLimits MS_CX1_MIN_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + container = (u16)msCompData.cx1; + ret = checkLimitsMinMax(&container, 1, 1, thresholds[0], + thresholds[1]); + /* check the limits */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMinMax MS CX1 failed... ERROR COUNT = %d\n", + ret); + pr_err("MS CX1 TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("MS CX1 TEST:.................OK\n\n"); + } else + pr_info("MS CX1 TEST:.................SKIPPED\n\n"); + + kfree(thresholds); + thresholds = NULL; + + pr_info("MS CX2 MIN MAX TEST:\n"); + if (todo->MutualCx2 == 1) { + ret = parseProductionTestLimits(path_limits, &limit_file, + MS_CX2_MAP_MIN, &thresholds_min, + &trows, &tcolumns); + /* load min thresholds */ + if (ret < 0 || (trows != msCompData.header.force_node || + tcolumns != msCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits MS_CX2_MAP_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = parseProductionTestLimits(path_limits, &limit_file, + MS_CX2_MAP_MAX, &thresholds_max, + &trows, &tcolumns); + /* load max thresholds */ + if (ret < 0 || (trows != msCompData.header.force_node || + tcolumns != msCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits MS_CX2_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMap(msCompData.node_data, + msCompData.header.force_node, + msCompData.header.sense_node, + thresholds_min, thresholds_max); + /* check the limits */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap MS CX2 MIN MAX failed... ERROR COUNT = %d\n", + ret); + pr_err("MS CX2 MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("MS CX2 MIN MAX TEST:.................OK\n\n"); + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else + pr_info("MS CX2 MIN MAX TEST:.................SKIPPED\n\n"); + + pr_info("MS CX2 ADJ TEST:\n"); + if (todo->MutualCx2Adj == 1) { + /* MS CX2 ADJ HORIZ */ + pr_info("MS CX2 ADJ HORIZ TEST:\n"); + + ret = computeAdjHoriz(msCompData.node_data, + msCompData.header.force_node, + msCompData.header.sense_node, + &adjhor); + if (ret < 0) { + pr_err("production_test_data: computeAdjHoriz failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + pr_info("MS CX2 ADJ HORIZ computed!\n"); + + ret = parseProductionTestLimits(path_limits, &limit_file, + MS_CX2_ADJH_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + if (ret < 0 || (trows != msCompData.header.force_node || + tcolumns != msCompData.header.sense_node - 1)) { + pr_err("production_test_data: parseProductionTestLimits MS_CX2_ADJH_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdj(adjhor, msCompData.header.force_node, + msCompData.header.sense_node - 1, + thresholds_max); + if (ret != OK) { + pr_err("production_test_data: checkLimitsMapAdj CX2 ADJH failed... ERROR COUNT = %d\n", + ret); + pr_err("MS CX2 ADJ HORIZ TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("MS CX2 ADJ HORIZ TEST:.................OK\n\n"); + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(adjhor); + adjhor = NULL; + + /* MS CX2 ADJ VERT */ + pr_info("MS CX2 ADJ VERT TEST:\n"); + + ret = computeAdjVert(msCompData.node_data, + msCompData.header.force_node, + msCompData.header.sense_node, + &adjvert); + if (ret < 0) { + pr_err("production_test_data: computeAdjVert failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + pr_info("MS CX2 ADJ VERT computed!\n"); + + ret = parseProductionTestLimits(path_limits, &limit_file, + MS_CX2_ADJV_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + if (ret < 0 || (trows != msCompData.header.force_node - 1 || + tcolumns != msCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits MS_CX2_ADJV_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdj(adjvert, msCompData.header.force_node - + 1, msCompData.header.sense_node - 1, + thresholds_max); + if (ret != OK) { + pr_err("production_test_data: checkLimitsMapAdj CX2 ADJV failed... ERROR COUNT = %d\n", + ret); + pr_err("MS CX2 ADJ HORIZ TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("MS CX2 ADJ VERT TEST:.................OK\n\n"); + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(adjvert); + adjvert = NULL; + } else + pr_info("MS CX2 ADJ TEST:.................SKIPPED\n\n"); + + /* START OF TOTAL CHECK */ + pr_info("MS TOTAL CX TEST:\n"); + + if (todo->MutualCxTotal == 1 || todo->MutualCxTotalAdj == 1) { + pr_info("MS TOTAL CX MIN MAX TEST:\n"); + if (todo->MutualCxTotal == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, + MS_TOTAL_CX_MAP_MIN, + &thresholds_min, + &trows, &tcolumns); + /* load min thresholds */ + if (ret < 0 || (trows != + totCompData.header.force_node || + tcolumns != + totCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits MS_TOTAL_CX_MAP_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = parseProductionTestLimits(path_limits, + &limit_file, + MS_TOTAL_CX_MAP_MAX, + &thresholds_max, + &trows, &tcolumns); + /* load max thresholds */ + if (ret < 0 || (trows != + totCompData.header.force_node || + tcolumns != + totCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits MS_TOTAL_CX_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapTotal(totCompData.node_data, + totCompData.header.force_node, + totCompData.header.sense_node, + thresholds_min, + thresholds_max); + /* check the limits */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap MS TOTAL CX TEST failed... ERROR COUNT = %d\n", + ret); + pr_err("MS TOTAL CX MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("MS TOTAL CX MIN MAX TEST:.................OK\n\n"); + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else + pr_info("MS TOTAL CX MIN MAX TEST:.................SKIPPED\n\n"); + + + pr_info("MS TOTAL CX ADJ TEST:\n"); + if (todo->MutualCxTotalAdj == 1) { + /* MS TOTAL CX ADJ HORIZ */ + pr_info("MS TOTAL CX ADJ HORIZ TEST:\n"); + + ret = computeAdjHorizTotal(totCompData.node_data, + totCompData.header.force_node, + totCompData.header.sense_node, + &total_adjhor); + if (ret < 0) { + pr_err("production_test_data: computeAdjHoriz failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + pr_info("MS TOTAL CX ADJ HORIZ computed!\n"); + + ret = parseProductionTestLimits(path_limits, + &limit_file, + MS_TOTAL_CX_ADJH_MAP_MAX, + &thresholds_max, + &trows, &tcolumns); + if (ret < 0 || (trows != + totCompData.header.force_node || + tcolumns != + totCompData.header.sense_node - 1)) { + pr_err("production_test_data: parseProductionTestLimits MS_TOTAL_CX_ADJH_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdjTotal(total_adjhor, + totCompData.header. + force_node, + totCompData.header. + sense_node - 1, + thresholds_max); + if (ret != OK) { + pr_err("production_test_data: checkLimitsMapAdj MS TOTAL CX ADJH failed... ERROR COUNT = %d\n", + ret); + pr_err("MS TOTAL CX ADJ HORIZ TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("MS TOTAL CX ADJ HORIZ TEST:.................OK\n\n"); + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(total_adjhor); + total_adjhor = NULL; + + /* MS TOTAL CX ADJ VERT */ + pr_info("MS TOTAL CX ADJ VERT TEST:\n"); + + ret = computeAdjVertTotal(totCompData.node_data, + totCompData.header.force_node, + totCompData.header.sense_node, + &total_adjvert); + if (ret < 0) { + pr_err("production_test_data: computeAdjVert failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + pr_info("MS TOTAL CX ADJ VERT computed!\n"); + + ret = parseProductionTestLimits(path_limits, + &limit_file, + MS_TOTAL_CX_ADJV_MAP_MAX, + &thresholds_max, + &trows, &tcolumns); + if (ret < 0 || (trows != totCompData.header.force_node - + 1 || tcolumns != + totCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits MS_TOTAL_CX_ADJV_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdjTotal(total_adjvert, + totCompData.header. + force_node - 1, + totCompData.header. + sense_node - 1, + thresholds_max); + if (ret != OK) { + pr_err("production_test_data: checkLimitsMapAdj MS TOTAL CX ADJV failed... ERROR COUNT = %d\n", + ret); + pr_err("MS TOTAL CX ADJ HORIZ TEST:.................FAIL\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("MS TOTAL CX ADJ VERT TEST:.................OK\n"); + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(total_adjvert); + total_adjvert = NULL; + } else + pr_info("MS TOTAL CX ADJ TEST:.................SKIPPED\n"); + + kfree(totCompData.node_data); + totCompData.node_data = NULL; + } else + pr_info("MS TOTAL CX TEST:.................SKIPPED\n"); + + if ((todo->MutualCx1LP | todo->MutualCx2LP | todo->MutualCx2AdjLP | + todo->MutualCxTotalLP | todo->MutualCxTotalAdjLP) == 1) { + ret = production_test_ms_cx_lp(path_limits, stop_on_fail, todo); + if (ret < OK) { + count_fail += 1; + pr_err("production_test_data: production_test_cx_lp failed... ERROR = %08X\n", + ret); + pr_err("MS CX testes finished!.................FAILED fails_count = %d\n\n", + count_fail); + return ret; + } + } else + pr_info("%s MS CX LP TEST:.................SKIPPED\n"); + + if ((todo->MutualKeyCx1 | todo->MutualKeyCx2 | + todo->MutualKeyCxTotal) == 1) { + ret = production_test_ms_key_cx(path_limits, stop_on_fail, + todo); + if (ret < 0) { + count_fail += 1; + pr_err("production_test_data: production_test_ms_key_cx failed... ERROR = %08X\n", + ret); + pr_err("MS CX testes finished!.................FAILED fails_count = %d\n\n", + count_fail); + return ret; + } + } else + pr_info("MS KEY CX TEST:.................SKIPPED\n"); + +ERROR: + + if (count_fail == 0) { + pr_info("MS CX testes finished!.................OK\n"); + kfree(msCompData.node_data); + msCompData.node_data = NULL; + return OK; + } else { + print_frame_i8("MS Init Data (Cx2) =", array1dTo2d_i8( + msCompData.node_data, + msCompData.node_data_size, + msCompData.header.sense_node), + msCompData.header.force_node, + msCompData.header.sense_node); + print_frame_short(" TOT MS Init Data (Cx) =", array1dTo2d_short( + totCompData.node_data, + totCompData.node_data_size, + totCompData.header.sense_node), + totCompData.header.force_node, + totCompData.header.sense_node); + pr_err("MS CX testes finished!.................FAILED fails_count = %d\n\n", + count_fail); + if (thresholds != NULL) + kfree(thresholds); + if (thresholds_min != NULL) + kfree(thresholds_min); + if (thresholds_max != NULL) + kfree(thresholds_max); + if (adjhor != NULL) + kfree(adjhor); + if (adjvert != NULL) + kfree(adjvert); + if (totCompData.node_data != NULL) + kfree(totCompData.node_data); + if (total_adjhor != NULL) + kfree(total_adjhor); + if (total_adjvert != NULL) + kfree(total_adjvert); + if (msCompData.node_data != NULL) + kfree(msCompData.node_data); + return ERROR_TEST_CHECK_FAIL | ERROR_PROD_TEST_DATA; + } + +ERROR_LIMITS: + if (thresholds != NULL) + kfree(thresholds); + if (thresholds_min != NULL) + kfree(thresholds_min); + if (thresholds_max != NULL) + kfree(thresholds_max); + if (adjhor != NULL) + kfree(adjhor); + if (adjvert != NULL) + kfree(adjvert); + if (totCompData.node_data != NULL) + kfree(totCompData.node_data); + if (total_adjhor != NULL) + kfree(total_adjhor); + if (total_adjvert != NULL) + kfree(total_adjvert); + if (msCompData.node_data != NULL) + kfree(msCompData.node_data); + return ret; +} + +/** + * Perform all the tests selected in a TestTodo variable related to MS Init + * data of the keys + * @param path_limits name of Production Limit file to load or + * "NULL" if the limits data should be loaded by a .h file + * @param stop_on_fail if 1, the test flow stops at the first data check + * failure + * otherwise it keeps going performing all the selected test + * @param todo pointer to a TestToDo variable which select the test to do + * @return OK if success or an error code which specify the type of error + */ +int production_test_ms_key_cx(const char *path_limits, int stop_on_fail, + TestToDo *todo) +{ + int ret; + int count_fail = 0; + int num_keys = 0; + + int *thresholds = NULL; + int *thresholds_min = NULL; + int *thresholds_max = NULL; + int trows, tcolumns; + + MutualSenseData msCompData; + TotMutualSenseData totCompData; + + + short container; + + + /* MS CX TEST */ + pr_info("MS KEY CX Testes are starting...\n"); + + ret = readMutualSenseCompensationData(LOAD_CX_MS_KEY, &msCompData); + /* read MS compensation data */ + if (ret < 0) { + pr_err("production_test_data: readMutualSenseCompensationData failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + return ret | ERROR_PROD_TEST_DATA; + } + + if (msCompData.header.force_node > msCompData.header.sense_node) + /* the meaningful data are only in the first row, + * the other rows are only a copy of the first one */ + num_keys = msCompData.header.force_node; + else + num_keys = msCompData.header.sense_node; + + pr_info("MS KEY CX1 TEST:\n"); + if (todo->MutualKeyCx1 == 1) { + ret = parseProductionTestLimits(path_limits, &limit_file, + MS_KEY_CX1_MIN_MAX, &thresholds, + &trows, &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + pr_err("production_test_data: parseProductionTestLimits MS_KEY_CX1_MIN_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + container = (short)msCompData.cx1; + ret = checkLimitsMinMax(&container, 1, 1, thresholds[0], + thresholds[1]); + /* check the limits */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMinMax MS CX1 failed... ERROR COUNT = %d\n", + ret); + pr_err("MS KEY CX1 TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("MS KEY CX1 TEST:.................OK\n\n"); + } else + pr_info("MS KEY CX1 TEST:.................SKIPPED\n\n"); + + kfree(thresholds); + thresholds = NULL; + + pr_info("MS KEY CX2 TEST:\n"); + if (todo->MutualKeyCx2 == 1) { + ret = parseProductionTestLimits(path_limits, &limit_file, + MS_KEY_CX2_MAP_MIN, + &thresholds_min, &trows, + &tcolumns); + /* load min thresholds */ + if (ret < 0 || (trows != msCompData.header.force_node || + tcolumns != msCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits MS_KEY_CX2_MAP_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = parseProductionTestLimits(path_limits, &limit_file, + MS_KEY_CX2_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + /* load max thresholds */ + if (ret < 0 || (trows != msCompData.header.force_node || + tcolumns != msCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits MS_KEY_CX2_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMap(msCompData.node_data, + msCompData.header.force_node, + msCompData.header.sense_node, + thresholds_min, thresholds_max); + /* check the limits */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap MS KEY CX2 failed... ERROR COUNT = %d\n", + ret); + pr_err("MS KEY CX2 TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("MS KEY CX2 TEST:.................OK\n\n"); + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else + pr_info("MS CX2 TEST:.................SKIPPED\n\n"); + + /* START OF TOTAL CHECK */ + pr_info("MS KEY TOTAL CX TEST:\n"); + + if (todo->MutualKeyCxTotal == 1) { + ret = readTotMutualSenseCompensationData( + LOAD_PANEL_CX_TOT_MS_KEY, &totCompData); + if (ret < 0) { + pr_err("production_test_data: computeTotalCx failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = parseProductionTestLimits(path_limits, &limit_file, + MS_KEY_TOTAL_CX_MAP_MIN, + &thresholds_min, &trows, + &tcolumns); + /* load min thresholds */ + if (ret < 0 || (trows != totCompData.header.force_node || + tcolumns != totCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits MS_KEY_TOTAL_CX_MAP_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = parseProductionTestLimits(path_limits, &limit_file, + MS_KEY_TOTAL_CX_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + /* load max thresholds */ + if (ret < 0 || (trows != totCompData.header.force_node || + tcolumns != totCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits MS_KEY_TOTAL_CX_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapTotal(totCompData.node_data, + totCompData.header.force_node, + totCompData.header.sense_node, + thresholds_min, thresholds_max); + /* check the limits */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap MS TOTAL KEY CX TEST failed... ERROR COUNT = %d\n", + ret); + pr_err("MS KEY TOTAL CX TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("MS KEY TOTAL CX TEST:.................OK\n\n"); + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + + kfree(totCompData.node_data); + totCompData.node_data = NULL; + } else + pr_info("MS KEY TOTAL CX TEST:.................SKIPPED\n"); + + +ERROR: + if (count_fail == 0) { + pr_info("MS KEY CX testes finished!.................OK\n"); + kfree(msCompData.node_data); + msCompData.node_data = NULL; + return OK; + } else { + print_frame_i8("MS Key Init Data (Cx2) =", array1dTo2d_i8( + msCompData.node_data, + msCompData.node_data_size, + msCompData.header.sense_node), + msCompData.header.force_node, + msCompData.header.sense_node); + pr_err("MS Key CX testes finished!.................FAILED fails_count = %d\n\n", + count_fail); + if (thresholds != NULL) + kfree(thresholds); + if (thresholds_min != NULL) + kfree(thresholds_min); + if (thresholds_max != NULL) + kfree(thresholds_max); + if (msCompData.node_data != NULL) + kfree(msCompData.node_data); + if (totCompData.node_data != NULL) + kfree(totCompData.node_data); + return ERROR_TEST_CHECK_FAIL | ERROR_PROD_TEST_DATA; + } + +ERROR_LIMITS: + if (thresholds != NULL) + kfree(thresholds); + if (thresholds_min != NULL) + kfree(thresholds_min); + if (thresholds_max != NULL) + kfree(thresholds_max); + if (msCompData.node_data != NULL) + kfree(msCompData.node_data); + if (totCompData.node_data != NULL) + kfree(totCompData.node_data); + return ret; +} + +/** + * Perform all the tests selected in a TestTodo variable related to MS LowPower + * Init data (touch, keys etc..) + * @param path_limits name of Production Limit file to load or + * "NULL" if the limits data should be loaded by a .h file + * @param stop_on_fail if 1, the test flow stops at the first data check + * failure otherwise it keeps going performing all the selected test + * @param todo pointer to a TestToDo variable which select the test to do + * @return OK if success or an error code which specify the type of error + */ +int production_test_ms_cx_lp(const char *path_limits, int stop_on_fail, + TestToDo *todo) +{ + int ret; + int count_fail = 0; + + int *thresholds = NULL; + int *thresholds_min = NULL; + int *thresholds_max = NULL; + int trows, tcolumns; + + MutualSenseData msCompData; + TotMutualSenseData totCompData; + + u8 *adjhor = NULL; + + u8 *adjvert = NULL; + + u16 container; + /* u16 *total_cx = NULL; */ + u16 *total_adjhor = NULL; + u16 *total_adjvert = NULL; + + + /* MS CX TEST */ + pr_info("MS LP CX Testes are starting...\n"); + + ret = readMutualSenseCompensationData(LOAD_CX_MS_LOW_POWER, + &msCompData); + /* read MS compensation data */ + if (ret < 0) { + pr_err("production_test_data: readMutualSenseCompensationData failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + return ret | ERROR_PROD_TEST_DATA; + } + + ret = readTotMutualSenseCompensationData(LOAD_PANEL_CX_TOT_MS_LOW_POWER, + &totCompData); + /* read TOT MS compensation data */ + if (ret < 0) { + pr_err("production_test_data: readTotMutualSenseCompensationData failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + kfree(msCompData.node_data); + msCompData.node_data = NULL; + return ret | ERROR_PROD_TEST_DATA; + } + + pr_info("MS LP CX1 TEST:\n"); + if (todo->MutualCx1LP == 1) { + ret = parseProductionTestLimits(path_limits, &limit_file, + MS_CX1_LP_MIN_MAX, &thresholds, + &trows, &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + pr_err("production_test_data: parseProductionTestLimits MS_CX1_LP_MIN_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + container = (u16)msCompData.cx1; + ret = checkLimitsMinMax(&container, 1, 1, thresholds[0], + thresholds[1]); + /* check the limits */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMinMax MS LP CX1 failed... ERROR COUNT = %d\n", + ret); + pr_err("MS LP CX1 TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("MS LP CX1 TEST:.................OK\n\n"); + } else + pr_info("MS LP CX1 TEST:.................SKIPPED\n\n"); + + kfree(thresholds); + thresholds = NULL; + + pr_info("%s MS LP CX2 MIN MAX TEST:\n"); + if (todo->MutualCx2LP == 1) { + ret = parseProductionTestLimits(path_limits, &limit_file, + MS_CX2_LP_MAP_MIN, + &thresholds_min, + &trows, &tcolumns); + /* load min thresholds */ + if (ret < 0 || (trows != msCompData.header.force_node || + tcolumns != msCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits MS_CX2_LP_MAP_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = parseProductionTestLimits(path_limits, &limit_file, + MS_CX2_LP_MAP_MAX, + &thresholds_max, + &trows, &tcolumns); + /* load max thresholds */ + if (ret < 0 || (trows != msCompData.header.force_node || + tcolumns != msCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits MS_CX2_LP_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMap(msCompData.node_data, + msCompData.header.force_node, + msCompData.header.sense_node, + thresholds_min, thresholds_max); + /* check the limits */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap MS LP CX2 MIN MAX failed... ERROR COUNT = %d\n", + ret); + pr_err("MS LP CX2 MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("MS LP CX2 MIN MAX TEST:.................OK\n\n"); + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else + pr_err("MS LP CX2 MIN MAX TEST:.................SKIPPED\n\n"); + + pr_info("MS LP CX2 ADJ TEST:\n"); + if (todo->MutualCx2AdjLP == 1) { + /* MS CX2 ADJ HORIZ */ + pr_info("MS LP CX2 ADJ HORIZ TEST:\n"); + + ret = computeAdjHoriz(msCompData.node_data, + msCompData.header.force_node, + msCompData.header.sense_node, + &adjhor); + if (ret < 0) { + pr_err("production_test_data: computeAdjHoriz failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + pr_info("MS LP CX2 ADJ HORIZ computed!\n"); + + ret = parseProductionTestLimits(path_limits, &limit_file, + MS_CX2_ADJH_LP_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + if (ret < 0 || (trows != msCompData.header.force_node || + tcolumns != msCompData.header.sense_node - 1)) { + pr_err("production_test_data: parseProductionTestLimits MS_CX2_ADJH_LP_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdj(adjhor, msCompData.header.force_node, + msCompData.header.sense_node - 1, + thresholds_max); + if (ret != OK) { + pr_err("production_test_data: checkLimitsMapAdj CX2 ADJH LP failed... ERROR COUNT = %d\n", + ret); + pr_err("MS LP CX2 ADJ HORIZ TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("MS LP CX2 ADJ HORIZ TEST:.................OK\n\n"); + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(adjhor); + adjhor = NULL; + + /* MS CX2 ADJ VERT */ + pr_info("MS LP CX2 ADJ VERT TEST:\n"); + + ret = computeAdjVert(msCompData.node_data, + msCompData.header.force_node, + msCompData.header.sense_node, + &adjvert); + if (ret < 0) { + pr_err("production_test_data: computeAdjVert failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + pr_info("MS LP CX2 ADJ VERT computed!\n"); + + ret = parseProductionTestLimits(path_limits, &limit_file, + MS_CX2_ADJV_LP_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + if (ret < 0 || (trows != msCompData.header.force_node - 1 || + tcolumns != msCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits MS_CX2_ADJV_LP_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdj(adjvert, msCompData.header.force_node - + 1, msCompData.header.sense_node - 1, + thresholds_max); + if (ret != OK) { + pr_err("production_test_data: checkLimitsMapAdj CX2 ADJV LP failed... ERROR COUNT = %d\n", + ret); + pr_err("MS LP CX2 ADJ HORIZ TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("MS LP CX2 ADJ VERT TEST:.................OK\n\n"); + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(adjvert); + adjvert = NULL; + } else + pr_info("MS LP CX2 ADJ TEST:.................SKIPPED\n\n"); + + /* START OF TOTAL CHECK */ + pr_info("MS TOTAL LP CX TEST:\n"); + + if (todo->MutualCxTotalLP == 1 || todo->MutualCxTotalAdjLP == 1) { + pr_info("MS TOTAL LP CX MIN MAX TEST:\n"); + if (todo->MutualCxTotalLP == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, + MS_TOTAL_CX_LP_MAP_MIN, + &thresholds_min, + &trows, &tcolumns); + /* load min thresholds */ + if (ret < 0 || (trows != + totCompData.header.force_node || + tcolumns != + totCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits MS_TOTAL_CX_LP_MAP_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = parseProductionTestLimits(path_limits, + &limit_file, + MS_TOTAL_CX_LP_MAP_MAX, + &thresholds_max, + &trows, &tcolumns); + /* load max thresholds */ + if (ret < 0 || (trows != + totCompData.header.force_node || + tcolumns != + totCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits MS_TOTAL_CX_LP_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapTotal(totCompData.node_data, + totCompData.header.force_node, + totCompData.header.sense_node, + thresholds_min, + thresholds_max); + /* check the limits */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap MS TOTAL CX LP TEST failed... ERROR COUNT = %d\n", + ret); + pr_err("MS TOTAL CX LP MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("MS TOTAL CX LP MIN MAX TEST:.................OK\n\n"); + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else + pr_info("MS TOTAL CX LP MIN MAX TEST:.................SKIPPED\n\n"); + + + pr_info("MS TOTAL CX ADJ LP TEST:\n"); + if (todo->MutualCxTotalAdjLP == 1) { + /* MS TOTAL CX ADJ HORIZ */ + pr_info("MS TOTAL CX ADJ HORIZ LP TEST:\n"); + + ret = computeAdjHorizTotal(totCompData.node_data, + totCompData.header.force_node, + totCompData.header.sense_node, + &total_adjhor); + if (ret < 0) { + pr_err("production_test_data: computeAdjHoriz failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + pr_info("MS TOTAL CX ADJ HORIZ LP computed!\n"); + + ret = parseProductionTestLimits(path_limits, + &limit_file, + MS_TOTAL_CX_ADJH_LP_MAP_MAX, + &thresholds_max, + &trows, &tcolumns); + if (ret < 0 || (trows != + totCompData.header.force_node || + tcolumns != + totCompData.header.sense_node - 1)) { + pr_err("production_test_data: parseProductionTestLimits MS_TOTAL_CX_ADJH_LP_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdjTotal(total_adjhor, + totCompData.header.force_node, + totCompData.header.sense_node - 1, + thresholds_max); + if (ret != OK) { + pr_err("production_test_data: checkLimitsMapAdj MS TOTAL CX ADJH LP failed... ERROR COUNT = %d\n", + ret); + pr_err("MS TOTAL CX ADJ HORIZ LP TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("MS TOTAL CX ADJ HORIZ LP TEST:.................OK\n\n"); + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(total_adjhor); + total_adjhor = NULL; + + /* MS TOTAL CX ADJ VERT */ + pr_info("MS TOTAL CX ADJ VERT LP TEST:\n"); + + ret = computeAdjVertTotal(totCompData.node_data, + totCompData.header.force_node, + totCompData.header.sense_node, + &total_adjvert); + if (ret < 0) { + pr_err("production_test_data: computeAdjVert failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + pr_info("MS TOTAL CX ADJ VERT LP computed!\n"); + + ret = parseProductionTestLimits(path_limits, + &limit_file, + MS_TOTAL_CX_ADJV_LP_MAP_MAX, + &thresholds_max, + &trows, &tcolumns); + if (ret < 0 || (trows != totCompData.header.force_node - + 1 || tcolumns != + totCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits MS_TOTAL_CX_ADJV_LP_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdjTotal(total_adjvert, + totCompData.header.force_node - 1, + totCompData.header.sense_node - 1, + thresholds_max); + if (ret != OK) { + pr_err("production_test_data: checkLimitsMapAdj MS TOTAL CX ADJV failed... ERROR COUNT = %d\n", + ret); + pr_err("MS TOTAL CX ADJ HORIZ LP TEST:.................FAIL\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("MS TOTAL CX ADJ VERT LP TEST:.................OK\n"); + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(total_adjvert); + total_adjvert = NULL; + } else + pr_info("MS TOTAL CX ADJ LP TEST:.................SKIPPED\n"); + + kfree(totCompData.node_data); + totCompData.node_data = NULL; + } else + pr_info("MS TOTAL CX LP TEST:.................SKIPPED\n"); + + + +ERROR: + + if (count_fail == 0) { + pr_info("MS LP CX testes finished!.................OK\n"); + kfree(msCompData.node_data); + msCompData.node_data = NULL; + return OK; + } + + print_frame_i8("MS LP Init Data (Cx2) =", array1dTo2d_i8( + msCompData.node_data, + msCompData.node_data_size, + msCompData.header.sense_node), + msCompData.header.force_node, + msCompData.header.sense_node); + print_frame_short(" TOT MS LP Init Data (Cx) =", + array1dTo2d_short( + totCompData.node_data, + totCompData.node_data_size, + totCompData.header.sense_node), + totCompData.header.force_node, + totCompData.header.sense_node); + pr_err("MS LP CX testes finished!.................FAILED fails_count = %d\n\n", + count_fail); + if (thresholds != NULL) + kfree(thresholds); + if (thresholds_min != NULL) + kfree(thresholds_min); + if (thresholds_max != NULL) + kfree(thresholds_max); + if (adjhor != NULL) + kfree(adjhor); + if (adjvert != NULL) + kfree(adjvert); + if (totCompData.node_data != NULL) + kfree(totCompData.node_data); + if (total_adjhor != NULL) + kfree(total_adjhor); + if (total_adjvert != NULL) + kfree(total_adjvert); + if (msCompData.node_data != NULL) + kfree(msCompData.node_data); + return ERROR_TEST_CHECK_FAIL | ERROR_PROD_TEST_DATA; + +ERROR_LIMITS: + if (thresholds != NULL) + kfree(thresholds); + if (thresholds_min != NULL) + kfree(thresholds_min); + if (thresholds_max != NULL) + kfree(thresholds_max); + if (adjhor != NULL) + kfree(adjhor); + if (adjvert != NULL) + kfree(adjvert); + if (totCompData.node_data != NULL) + kfree(totCompData.node_data); + if (total_adjhor != NULL) + kfree(total_adjhor); + if (total_adjvert != NULL) + kfree(total_adjvert); + if (msCompData.node_data != NULL) + kfree(msCompData.node_data); + return ret; +} + +/** + * Perform all the test selected in a TestTodo variable related to SS raw data + *(touch, keys etc..) + * @param path_limits name of Production Limit file to load or + * "NULL" if the limits data should be loaded by a .h file + * @param stop_on_fail if 1, the test flow stops at the first data check + * failure + * otherwise it keeps going performing all the selected test + * @param todo pointer to a TestToDo variable which select the test to do + * @return OK if success or an error code which specify the type of error + */ +int production_test_ss_raw(const char *path_limits, int stop_on_fail, + TestToDo *todo) +{ + int ret; + int count_fail = 0; + int rows, columns; + + SelfSenseFrame ssRawFrame; + + int *thresholds = NULL; + int *thresholds_min = NULL; + int *thresholds_max = NULL; + int trows, tcolumns; + + /* SS TEST */ + pr_info("SS RAW Testes are starting...\n"); + + /************** Self Sense Test **************/ + + pr_info("Getting SS Frame...\n"); + ret = setScanMode(SCAN_MODE_LOCKED, LOCKED_ACTIVE); + msleep(WAIT_FOR_FRESH_FRAMES); + ret |= setScanMode(SCAN_MODE_ACTIVE, 0x00); + msleep(WAIT_AFTER_SENSEOFF); +#ifdef READ_FILTERED_RAW + ret |= getSSFrame3(SS_FILTER, &ssRawFrame); +#else + ret |= getSSFrame3(SS_RAW, &ssRawFrame); +#endif + if (ret < 0) { + pr_err("production_test_data: getSSFrame failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + return ret | ERROR_PROD_TEST_DATA; + } + + print_frame_short("SS Raw force frame =", + array1dTo2d_short( + ssRawFrame.force_data, + ssRawFrame.header.force_node, + 1), + ssRawFrame.header.force_node, 1); + print_frame_short("SS Raw sense frame =", + array1dTo2d_short( + ssRawFrame.sense_data, + ssRawFrame.header.sense_node, + ssRawFrame.header.sense_node), + 1, ssRawFrame.header.sense_node); + + /* SS RAW (PROXIMITY) FORCE TEST */ + pr_info("SS RAW FORCE TEST:\n"); + + + + if (todo->SelfForceRaw == 1 || todo->SelfForceRawGap == 1 + || todo->SelfForceRawMap == 1) { + columns = 1; /* there are no data for the sense channels + * because is a force frame + */ + rows = ssRawFrame.header.force_node; + + pr_info("SS RAW FORCE MIN MAX TEST:\n"); + if (todo->SelfForceRaw == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_RAW_FORCE_MIN_MAX, + &thresholds, &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + pr_err("production_test_data: parseProductionTestLimits SS_RAW_FORCE_MIN_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMinMax(ssRawFrame.force_data, rows, + columns, thresholds[0], + thresholds[1]); + if (ret != OK) { + pr_err("production_test_data: checkLimitsMinMax SS RAW FORCE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS RAW (PROXIMITY) FORCE MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) { + ret = ERROR_PROD_TEST_DATA | + ERROR_TEST_CHECK_FAIL; + goto ERROR_LIMITS; + } + } else + pr_info("SS RAW FORCE MIN MAX TEST:.................OK\n\n"); + + kfree(thresholds); + thresholds = NULL; + } else + pr_info("SS RAW FORCE MIN MAX TEST:.................SKIPPED\n\n"); + + pr_info("SS RAW FORCE MAP MIN MAX TEST:\n"); + if (todo->SelfForceRawMap == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, SS_RAW_FORCE_EACH_NODE_MIN, + &thresholds_min, &trows, &tcolumns); + if (ret < OK || (trows != rows || + tcolumns != columns)) { + pr_err("production_test_data: parseProductionTestLimits SS_RAW_FORCE_EACH_NODE_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + ret = parseProductionTestLimits(path_limits, + &limit_file, SS_RAW_FORCE_EACH_NODE_MAX, + &thresholds_max, &trows, &tcolumns); + if (ret < OK || (trows != rows || + tcolumns != columns)) { + pr_err("production_test_data: parseProductionTestLimits SS_RAW_FORCE_EACH_NODE_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapTotal(ssRawFrame.force_data, rows, + columns, thresholds_min, + thresholds_max); + if (ret != OK) { + pr_err("production_test_data: checkLimitsMinMax SS RAW FORCE MAP failed... ERROR COUNT = %d\n", + ret); + pr_err("SS RAW FORCE MAP MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + print_frame_short("SS Raw force frame =", + array1dTo2d_short( + ssRawFrame.force_data, + rows * + columns, + columns), rows, + columns); + if (stop_on_fail) { + ret = ERROR_PROD_TEST_DATA | + ERROR_TEST_CHECK_FAIL; + goto ERROR_LIMITS; + } + } else + pr_info("SS RAW FORCE MAP MIN MAX TEST:.................OK\n\n"); + + if (thresholds_min != NULL) { + kfree(thresholds_min); + thresholds_min = NULL; + } + if (thresholds_max != NULL) { + kfree(thresholds_max); + thresholds_max = NULL; + } + } else + pr_info("SS RAW FORCE MAP MIN MAX TEST:.................SKIPPED\n\n"); + + pr_info("SS RAW FORCE GAP TEST:\n"); + if (todo->SelfForceRawGap == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_RAW_FORCE_GAP, + &thresholds, &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_RAW_FORCE_GAP failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsGap(ssRawFrame.force_data, rows, + columns, thresholds[0]); + if (ret != OK) { + pr_err("production_test_data: checkLimitsGap SS RAW FORCE GAP failed... ERROR = %08X\n", + ret); + pr_err("SS RAW FORCE GAP TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) { + ret = ERROR_PROD_TEST_DATA | + ERROR_TEST_CHECK_FAIL; + goto ERROR_LIMITS; + } + } else + pr_info("SS RAW FORCE GAP TEST:.................OK\n\n"); + + kfree(thresholds); + thresholds = NULL; + } else + pr_info("SS RAW FORCE GAP TEST:.................SKIPPED\n\n"); + + kfree(ssRawFrame.force_data); + ssRawFrame.force_data = NULL; + } else + pr_info("SS RAW FORCE TEST:.................SKIPPED\n\n"); + + /* SS RAW (PROXIMITY) SENSE TEST */ + pr_info("SS RAW SENSE TEST:\n"); + + if (todo->SelfSenseRaw == 1 || todo->SelfSenseRawGap == 1 || + todo->SelfSenseRawMap == 1) { + columns = ssRawFrame.header.sense_node; + rows = 1; /* there are no data for the force channels + * because is a sense frame + */ + + pr_info("SS RAW SENSE MIN MAX TEST:\n"); + if (todo->SelfSenseRaw == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_RAW_SENSE_MIN_MAX, + &thresholds, &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + pr_err("production_test_data: parseProductionTestLimits SS_RAW_SENSE_MIN_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMinMax(ssRawFrame.sense_data, rows, + columns, thresholds[0], + thresholds[1]); + if (ret != OK) { + pr_err("production_test_data: checkLimitsMinMax SS RAW SENSE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS RAW SENSE MIN MAX TEST:.................FAIL\n"); + count_fail += 1; + if (stop_on_fail) { + ret = ERROR_PROD_TEST_DATA | + ERROR_TEST_CHECK_FAIL; + goto ERROR_LIMITS; + } + } else + pr_info("SS RAW SENSE MIN MAX TEST:.................OK\n"); + + kfree(thresholds); + thresholds = NULL; + } else + pr_info("SS RAW SENSE MIN MAX TEST:.................SKIPPED\n"); + + pr_info("SS RAW SENSE MAP MIN MAX TEST:\n"); + if (todo->SelfSenseRawMap == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, SS_RAW_SENSE_EACH_NODE_MIN, + &thresholds_min, &trows, &tcolumns); + if (ret < OK || (trows != rows || + tcolumns != columns)) { + pr_err("production_test_data: parseProductionTestLimits SS_RAW_SENSE_EACH_NODE_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + ret = parseProductionTestLimits(path_limits, + &limit_file, SS_RAW_SENSE_EACH_NODE_MAX, + &thresholds_max, &trows, &tcolumns); + if (ret < OK || (trows != rows || + tcolumns != columns)) { + pr_err("production_test_data: parseProductionTestLimits SS_RAW_SENSE_EACH_NODE_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapTotal(ssRawFrame.sense_data, rows, + columns, thresholds_min, + thresholds_max); + if (ret != OK) { + pr_err("production_test_data: checkLimitsMinMax SS RAW SENSE MAP failed... ERROR COUNT = %d\n", + ret); + pr_err("SS RAW SENSE MAP MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + print_frame_short("SS Raw sense frame =", + array1dTo2d_short( + ssRawFrame.sense_data, + rows * + columns, + columns), rows, + columns); + if (stop_on_fail) { + ret = ERROR_PROD_TEST_DATA | + ERROR_TEST_CHECK_FAIL; + goto ERROR_LIMITS; + } + } else + pr_info("SS RAW SENSE MAP MIN MAX TEST:.................OK\n\n"); + + if (thresholds_min != NULL) { + kfree(thresholds_min); + thresholds_min = NULL; + } + if (thresholds_max != NULL) { + kfree(thresholds_max); + thresholds_max = NULL; + } + } else + pr_info("SS RAW SENSE MAP MIN MAX TEST:.................SKIPPED\n\n"); + + pr_info("SS RAW SENSE GAP TEST:\n"); + if (todo->SelfSenseRawGap == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_RAW_SENSE_GAP, + &thresholds, &trows, + &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_RAW_SENSE_GAP failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsGap(ssRawFrame.sense_data, rows, + columns, thresholds[0]); + if (ret != OK) { + pr_err("production_test_data: checkLimitsGap SS RAW SENSE GAP failed... ERROR = %08X\n", + ret); + pr_err("SS RAW SENSE GAP TEST:.................FAIL\n"); + count_fail += 1; + if (stop_on_fail) { + ret = ERROR_PROD_TEST_DATA | + ERROR_TEST_CHECK_FAIL; + goto ERROR_LIMITS; + } + } else + pr_info("SS RAW SENSE GAP TEST:.................OK\n"); + + kfree(thresholds); + thresholds = NULL; + } else + pr_info("SS RAW SENSE GAP TEST:.................SKIPPED\n"); + + kfree(ssRawFrame.sense_data); + ssRawFrame.sense_data = NULL; + } else + pr_info("SS RAW SENSE TEST:.................SKIPPED\n\n"); + + ret = production_test_ss_raw_lp(path_limits, stop_on_fail, todo); + if (ret < OK) { + pr_err("production_test_data: ss_raw_lp failed... ERROR = %08X\n", + ret); + count_fail += 1; + } + + if (count_fail == 0) { + pr_info("SS RAW testes finished!.................OK\n\n"); + return OK; + } + + pr_err("SS RAW testes finished!.................FAILED fails_count = %d\n\n", + count_fail); + return ERROR_TEST_CHECK_FAIL | ERROR_PROD_TEST_DATA; + + +ERROR_LIMITS: + if (ssRawFrame.force_data != NULL) + kfree(ssRawFrame.force_data); + if (ssRawFrame.sense_data != NULL) + kfree(ssRawFrame.sense_data); + if (thresholds != NULL) + kfree(thresholds); + if (thresholds_min != NULL) + kfree(thresholds_min); + if (thresholds_max != NULL) + kfree(thresholds_max); + + return ret; +} + + +/* + * Perform all the test selected in a TestTodo variable related to SS raw data + * low power + * @param path_limits name of Production Limit file to load or + * "NULL" if the limits data should be loaded by a .h file + * @param stop_on_fail if 1, the test flow stops at the first data check + * failure + * otherwise it keeps going performing all the selected test + * @param todo pointer to a TestToDo variable which select the test to do + * @return OK if success or an error code which specify the type of error + */ +int production_test_ss_raw_lp(const char *path_limits, int stop_on_fail, + TestToDo *todo) +{ + int ret; + int count_fail = 0; + int rows, columns; + + SelfSenseFrame ssRawFrame; + + int *thresholds = NULL; + int trows, tcolumns; + int *thresholds_min = NULL; + int *thresholds_max = NULL; + + /* SS TEST */ + pr_info("SS RAW LP Testes are starting...\n"); + + /************** Self Sense Test **************/ + + pr_info("Getting SS LP Frame...\n"); + ret = setScanMode(SCAN_MODE_LOCKED, LOCKED_LP_DETECT); + msleep(WAIT_FOR_FRESH_FRAMES); + ret |= setScanMode(SCAN_MODE_ACTIVE, 0x00); + msleep(WAIT_AFTER_SENSEOFF); +#ifdef READ_FILTERED_RAW + ret |= getSSFrame3(SS_DETECT_FILTER, &ssRawFrame); +#else + ret |= getSSFrame3(SS_DETECT_RAW, &ssRawFrame); +#endif + if (ret < 0) { + pr_err("production_test_data: getSSFrame failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + return ret | ERROR_PROD_TEST_DATA; + } + + print_frame_short("SS Raw LP force frame =", + array1dTo2d_short( + ssRawFrame.force_data, + ssRawFrame.header.force_node, 1), + ssRawFrame.header.force_node, 1); + print_frame_short("SS Raw LP sense frame =", + array1dTo2d_short( + ssRawFrame.sense_data, + ssRawFrame.header.sense_node, + ssRawFrame.header.sense_node), + 1, ssRawFrame.header.sense_node); + + /* SS RAW (PROXIMITY) FORCE TEST */ + pr_info("SS RAW LP FORCE TEST:\n"); + + if ((todo->SelfForceRawLP == 1 || todo->SelfForceRawGapLP == 1 || + todo->SelfForceRawMapLP == 1) && + ssRawFrame.header.force_node != 0) { + columns = 1; /* there are no data for the sense channels + * because is a force frame + */ + rows = ssRawFrame.header.force_node; + + pr_info("SS RAW LP FORCE MIN MAX TEST:\n"); + if (todo->SelfForceRawLP == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_RAW_LP_FORCE_MIN_MAX, + &thresholds, + &trows, &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + pr_err("production_test_data: parseProductionTestLimits SS_RAW_FORCE_MIN_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMinMax(ssRawFrame.force_data, rows, + columns, thresholds[0], + thresholds[1]); + if (ret != OK) { + pr_err("production_test_data: checkLimitsMinMax SS RAW FORCE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS RAW LP FORCE MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) { + ret = ERROR_PROD_TEST_DATA | + ERROR_TEST_CHECK_FAIL; + goto ERROR_LIMITS; + } + } else + pr_info("SS RAW LP FORCE MIN MAX TEST:.................OK\n\n"); + + kfree(thresholds); + thresholds = NULL; + } else + pr_info("SS RAW LP FORCE MIN MAX TEST:.................SKIPPED\n\n"); + + pr_info("SS RAW LP FORCE MAP MIN MAX TEST:\n"); + if (todo->SelfForceRawMapLP == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, SS_RAW_LP_FORCE_EACH_NODE_MIN, + &thresholds_min, &trows, &tcolumns); + if (ret < OK || (trows != rows || + tcolumns != columns)) { + pr_err("production_test_data: parseProductionTestLimits SS_RAW_LP_FORCE_EACH_NODE_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + ret = parseProductionTestLimits(path_limits, + &limit_file, SS_RAW_LP_FORCE_EACH_NODE_MAX, + &thresholds_max, &trows, &tcolumns); + if (ret < OK || (trows != rows || + tcolumns != columns)) { + pr_err("production_test_data: parseProductionTestLimits SS_RAW_LP_FORCE_EACH_NODE_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapTotal(ssRawFrame.force_data, rows, + columns, thresholds_min, + thresholds_max); + if (ret != OK) { + pr_err("production_test_data: checkLimitsMinMax SS RAW LP FORCE MAP failed... ERROR COUNT = %d\n", + ret); + pr_err("SS RAW LP FORCE MAP MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + print_frame_short("SS Raw LP force frame =", + array1dTo2d_short( + ssRawFrame.force_data, + rows * + columns, + columns), rows, + columns); + if (stop_on_fail) { + ret = ERROR_PROD_TEST_DATA | + ERROR_TEST_CHECK_FAIL; + goto ERROR_LIMITS; + } + } else + pr_info("SS RAW FORCE LP MAP MIN MAX TEST:.................OK\n\n"); + + if (thresholds_min != NULL) { + kfree(thresholds_min); + thresholds_min = NULL; + } + if (thresholds_max != NULL) { + kfree(thresholds_max); + thresholds_max = NULL; + } + } else + pr_info("SS RAW FORCE LP MAP MIN MAX TEST:.................SKIPPED\n\n"); + + pr_info("SS RAW LP FORCE GAP TEST:\n"); + if (todo->SelfForceRawGapLP == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_RAW_LP_FORCE_GAP, + &thresholds, &trows, + &tcolumns); + if (ret < OK || (trows != 1 || tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_RAW_FORCE_GAP failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsGap(ssRawFrame.force_data, rows, + columns, thresholds[0]); + if (ret != OK) { + pr_err("production_test_data: checkLimitsGap SS RAW FORCE GAP failed... ERROR = %08X\n", + ret); + pr_err("SS RAW LP FORCE GAP TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) { + ret = ERROR_PROD_TEST_DATA | + ERROR_TEST_CHECK_FAIL; + goto ERROR_LIMITS; + } + } else + pr_info("SS RAW LP FORCE GAP TEST:.................OK\n\n"); + + kfree(thresholds); + thresholds = NULL; + } else + pr_info("SS RAW LP FORCE GAP TEST:.................SKIPPED\n\n"); + + kfree(ssRawFrame.force_data); + ssRawFrame.force_data = NULL; + } else + pr_info("SS RAW LP FORCE TEST:.................SKIPPED\n\n"); + + /* SS RAW (PROXIMITY) SENSE TEST */ + pr_info("SS RAW LP SENSE TEST:\n"); + + if ((todo->SelfSenseRawLP == 1 || todo->SelfSenseRawGapLP == 1 || + todo->SelfSenseRawMapLP == 1) && + ssRawFrame.header.sense_node != 0){ + columns = ssRawFrame.header.sense_node; + rows = 1; /* there are no data for the force channels + * because is a sense frame + */ + + pr_info("SS RAW LP SENSE MIN MAX TEST:\n"); + if (todo->SelfSenseRawLP == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_RAW_LP_SENSE_MIN_MAX, + &thresholds, + &trows, &tcolumns); + if (ret < OK || (trows != 1 || tcolumns != 2)) { + pr_err("production_test_data: parseProductionTestLimits SS_RAW_LP_SENSE_MIN_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMinMax(ssRawFrame.sense_data, rows, + columns, thresholds[0], + thresholds[1]); + if (ret != OK) { + pr_err("production_test_data: checkLimitsMinMax SS RAW LP SENSE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS RAW LP SENSE MIN MAX TEST:.................FAIL\n"); + count_fail += 1; + if (stop_on_fail) { + ret = ERROR_PROD_TEST_DATA | + ERROR_TEST_CHECK_FAIL; + goto ERROR_LIMITS; + } + } else + pr_info("SS RAW SENSE MIN MAX TEST:.................OK\n"); + + kfree(thresholds); + thresholds = NULL; + } else + pr_info("SS RAW LP SENSE MIN MAX TEST:.................SKIPPED\n"); + + pr_info("SS RAW LP SENSE MAP MIN MAX TEST:\n"); + if (todo->SelfSenseRawMapLP == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, SS_RAW_LP_SENSE_EACH_NODE_MIN, + &thresholds_min, &trows, &tcolumns); + if (ret < OK || (trows != rows || + tcolumns != columns)) { + pr_err("production_test_data: parseProductionTestLimits SS_RAW_LP_SENSE_EACH_NODE_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + ret = parseProductionTestLimits(path_limits, + &limit_file, SS_RAW_LP_SENSE_EACH_NODE_MAX, + &thresholds_max, &trows, &tcolumns); + if (ret < OK || (trows != rows || + tcolumns != columns)) { + pr_err("production_test_data: parseProductionTestLimits SS_RAW_LP_SENSE_EACH_NODE_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapTotal(ssRawFrame.sense_data, rows, + columns, thresholds_min, + thresholds_max); + if (ret != OK) { + pr_err("production_test_data: checkLimitsMinMax SS RAW LP SENSE MAP failed... ERROR COUNT = %d\n", + ret); + pr_err("SS RAW LP SENSE MAP MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + print_frame_short("SS Raw LP sense frame =", + array1dTo2d_short( + ssRawFrame.sense_data, + rows * + columns, + columns), rows, + columns); + if (stop_on_fail) { + ret = ERROR_PROD_TEST_DATA | + ERROR_TEST_CHECK_FAIL; + goto ERROR_LIMITS; + } + } else + pr_info("SS RAW LP SENSE MAP MIN MAX TEST:.................OK\n\n"); + + if (thresholds_min != NULL) { + kfree(thresholds_min); + thresholds_min = NULL; + } + if (thresholds_max != NULL) { + kfree(thresholds_max); + thresholds_max = NULL; + } + } else + pr_info("SS RAW LP SENSE MAP MIN MAX TEST:.................SKIPPED\n\n"); + + pr_info("SS RAW LP SENSE GAP TEST:\n"); + if (todo->SelfSenseRawGapLP == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_RAW_LP_SENSE_GAP, + &thresholds, &trows, + &tcolumns); + if (ret < OK || (trows != 1 || tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_RAW_LP_SENSE_GAP failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsGap(ssRawFrame.sense_data, rows, + columns, thresholds[0]); + if (ret != OK) { + pr_err("production_test_data: checkLimitsGap SS RAW LP SENSE GAP failed... ERROR = %08X\n", + ret); + pr_err("SS RAW LP SENSE GAP TEST:.................FAIL\n"); + count_fail += 1; + if (stop_on_fail) { + ret = ERROR_PROD_TEST_DATA | + ERROR_TEST_CHECK_FAIL; + goto ERROR_LIMITS; + } + } else + pr_info("SS RAW LP SENSE GAP TEST:.................OK\n"); + + kfree(thresholds); + thresholds = NULL; + } else + pr_info("SS RAW LP SENSE GAP TEST:.................SKIPPED\n"); + + kfree(ssRawFrame.sense_data); + ssRawFrame.sense_data = NULL; + } else + pr_info("SS RAW LP SENSE TEST:.................SKIPPED\n\n"); + + if (count_fail == 0) { + pr_info("SS RAW LP testes finished!.................OK\n\n"); + return OK; + } + + pr_err("SS RAW LP testes finished!.................FAILED fails_count = %d\n\n", + count_fail); + return ERROR_TEST_CHECK_FAIL | ERROR_PROD_TEST_DATA; + +ERROR_LIMITS: + if (ssRawFrame.force_data != NULL) + kfree(ssRawFrame.force_data); + if (ssRawFrame.sense_data != NULL) + kfree(ssRawFrame.sense_data); + if (thresholds != NULL) + kfree(thresholds); + if (thresholds_min != NULL) + kfree(thresholds_min); + if (thresholds_max != NULL) + kfree(thresholds_max); + + return ret; +} + +/* + * Perform all the tests selected in a TestTodo variable related to SS Init + * data (touch, keys etc..) + * @param path_limits name of Production Limit file to load or + * "NULL" if the limits data should be loaded by a .h file + * @param stop_on_fail if 1, the test flow stops at the first data check + * failure + * otherwise it keeps going performing all the selected test + * @param todo pointer to a TestToDo variable which select the test to do + * @return OK if success or an error code which specify the type of error + */ +int production_test_ss_ix_cx(const char *path_limits, int stop_on_fail, + TestToDo *todo) +{ + int ret; + int count_fail = 0; + + int *thresholds = NULL; + int trows, tcolumns; + int *thresholds_min = NULL; + int *thresholds_max = NULL; + + SelfSenseData ssCompData; + TotSelfSenseData totCompData; + + u8 *adjhor = NULL; + u8 *adjvert = NULL; + + short container; + + u16 *total_adjhor = NULL; + u16 *total_adjvert = NULL; + + pr_info("SS IX CX testes are starting...\n"); + ret = readSelfSenseCompensationData(LOAD_CX_SS_TOUCH, &ssCompData); + /* read the SS compensation data */ + if (ret < 0) { + pr_err("production_test_data: readSelfSenseCompensationData failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + return ret | ERROR_PROD_TEST_DATA; + } + + ret = readTotSelfSenseCompensationData(LOAD_PANEL_CX_TOT_SS_TOUCH, + &totCompData); + /* read the TOT SS compensation data */ + if (ret < 0) { + pr_err("production_test_data: readTotSelfSenseCompensationData failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + kfree(ssCompData.ix2_fm); + kfree(ssCompData.ix2_sn); + kfree(ssCompData.cx2_fm); + kfree(ssCompData.cx2_sn); + return ret | ERROR_PROD_TEST_DATA; + } + + /************* SS FORCE IX **************/ + /* SS IX1 FORCE TEST */ + pr_info("SS IX1 FORCE TEST:\n"); + if (todo->SelfForceIx1 == 1) { + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_IX1_FORCE_MIN_MAX, + &thresholds, &trows, &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + pr_err("production_test_data: parseProductionTestLimits SS_IX1_FORCE_MIN_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + container = (short)ssCompData.f_ix1; + ret = checkLimitsMinMax(&container, 1, 1, thresholds[0], + thresholds[1]); + /* check the limits */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMinMax SS IX1 FORCE TEST failed... ERROR COUNT = %d\n", + ret); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS IX1 FORCE TEST:.................OK\n\n"); + } else + pr_info("SS IX1 FORCE TEST:.................SKIPPED\n\n"); + + kfree(thresholds); + thresholds = NULL; + /* SS IX2 FORCE TEST */ + pr_info("SS IX2 FORCE MIN MAX TEST:\n"); + if (todo->SelfForceIx2 == 1) { + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_IX2_FORCE_MAP_MIN, + &thresholds_min, &trows, + &tcolumns); + /* load the min thresholds */ + if (ret < 0 || (trows != ssCompData.header.force_node || + tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_IX2_FORCE_MAP_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_IX2_FORCE_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != ssCompData.header.force_node || + tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_IX2_FORCE_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapFromU(ssCompData.ix2_fm, + ssCompData.header.force_node, 1, + thresholds_min, + thresholds_max); /* check the + * values with + * thresholds + */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap SS IX2 FORCE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS IX2 FORCE MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS IX2 FORCE MIN MAX TEST:.................OK\n\n"); + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else + pr_info("SS IX2 FORCE MIN MAX TEST:.................SKIPPED\n\n"); + + pr_info("SS IX2 FORCE ADJ TEST:\n"); + if (todo->SelfForceIx2Adj == 1) { + /* SS IX2 FORCE ADJV TEST */ + pr_info("SS IX2 FORCE ADJVERT TEST:\n"); + ret = computeAdjVertFromU(ssCompData.ix2_fm, + ssCompData.header.force_node, 1, + &adjvert); + if (ret < 0) { + pr_err("production_test_data: computeAdjVert SS IX2 FORCE ADJV failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + pr_info("SS IX2 FORCE ADJV computed!\n"); + + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_IX2_FORCE_ADJV_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); /* load the max + * thresholds + */ + if (ret < 0 || (trows != ssCompData.header.force_node - 1 || + tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_IX2_FORCE_ADJV_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdj(adjvert, ssCompData.header.force_node - + 1, 1, thresholds_max); /* check the + * values with + * thresholds + */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap SS IX2 FORCE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS IX2 FORCE ADJV TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS IX2 FORCE ADJV TEST:.................OK\n\n"); + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(adjvert); + adjvert = NULL; + } else + pr_info("SS IX2 FORCE ADJ TEST:.................SKIPPED\n\n"); + + /* SS TOTAL FORCE IX */ + pr_info("SS TOTAL IX FORCE TEST:\n"); + if (todo->SelfForceIxTotal == 1 || todo->SelfForceIxTotalAdj == 1) { + pr_info("SS TOTAL IX FORCE MIN MAX TEST:\n"); + if (todo->SelfForceIxTotal == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_TOTAL_IX_FORCE_MAP_MIN, + &thresholds_min, + &trows, &tcolumns); + /* load the min thresholds */ + if (ret < 0 || (trows != + totCompData.header.force_node || + tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_TOTAL_IX_FORCE_MAP_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_TOTAL_IX_FORCE_MAP_MAX, + &thresholds_max, + &trows, &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != + totCompData.header.force_node || + tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_TOTAL_IX_FORCE_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapTotalFromU(totCompData.ix_fm, + totCompData.header.force_node, + 1, + thresholds_min, + thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap SS TOTAL IX FORCE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS TOTAL IX FORCE MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS TOTAL IX FORCE MIN MAX TEST:.................OK\n\n"); + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else + pr_info("SS TOTAL IX FORCE MIN MAX TEST:.................SKIPPED\n"); + + pr_info("SS TOTAL IX FORCE ADJ TEST:\n"); + if (todo->SelfForceIxTotalAdj == 1) { + /* SS TOTAL IX FORCE ADJV TEST */ + pr_info("SS TOTAL IX FORCE ADJVERT TEST:\n"); + ret = computeAdjVertTotalFromU(totCompData.ix_fm, + totCompData.header.force_node, 1, + &total_adjvert); + if (ret < 0) { + pr_err("production_test_data: computeAdjVert SS TOTAL IX FORCE ADJV failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + pr_info("SS TOTAL IX FORCE ADJV computed!\n"); + + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_TOTAL_IX_FORCE_ADJV_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != totCompData.header.force_node - + 1 || tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_TOTAL_IX_FORCE_ADJV_MAP_MAX... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdjTotal(total_adjvert, + totCompData.header.force_node - 1, 1, + thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap SS TOTAL IX FORCE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS TOTAL IX FORCE ADJV TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS TOTAL IX FORCE ADJV TEST:.................OK\n\n"); + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(total_adjvert); + total_adjvert = NULL; + } else + pr_info("SS TOTAL IX FORCE ADJ TEST:.................SKIPPED\n"); + } else + pr_info("SS TOTAL IX FORCE TEST:.................SKIPPED\n\n"); + + + /************** SS SENSE IX **************/ + /* SS IX1 SENSE TEST */ + pr_info("SS IX1 SENSE TEST:\n"); + if (todo->SelfSenseIx1 == 1) { + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_IX1_SENSE_MIN_MAX, + &thresholds, &trows, &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + pr_err("production_test_data: parseProductionTestLimits SS_IX1_SENSE_MIN_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + container = (short)ssCompData.s_ix1; + ret = checkLimitsMinMax(&container, 1, 1, thresholds[0], + thresholds[1]); + /* check the limits */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMinMax SS IX1 SENSE TEST failed... ERROR COUNT = %d\n", + ret); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS IX1 SENSE TEST:.................OK\n\n"); + } else + pr_info("SS IX1 SENSE TEST:.................SKIPPED\n\n"); + + kfree(thresholds); + thresholds = NULL; + /* SS IX2 SENSE TEST */ + pr_info("SS IX2 SENSE MIN MAX TEST:\n"); + if (todo->SelfSenseIx2 == 1) { + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_IX2_SENSE_MAP_MIN, + &thresholds_min, &trows, + &tcolumns); + /* load the min thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != + ssCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits SS_IX2_SENSE_MAP_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_IX2_SENSE_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != + ssCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits SS_IX2_SENSE_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapFromU(ssCompData.ix2_sn, 1, + ssCompData.header.sense_node, + thresholds_min, thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap SS IX2 SENSE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS IX2 SENSE MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS IX2 SENSE MIN MAX TEST:.................OK\n\n"); + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else + pr_info("SS IX2 SENSE MIN MAX TEST:.................SKIPPED\n\n"); + + pr_info("SS IX2 SENSE ADJ TEST:\n"); + if (todo->SelfSenseIx2Adj == 1) { + /* SS IX2 SENSE ADJH TEST */ + pr_info("SS IX2 SENSE ADJHORIZ TEST:\n"); + ret = computeAdjHorizFromU(ssCompData.ix2_sn, 1, + ssCompData.header.sense_node, + &adjhor); + if (ret < 0) { + pr_err("production_test_data: computeAdjHoriz SS IX2 SENSE ADJH failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + pr_info("SS IX2 SENSE ADJ HORIZ computed!\n"); + + + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_IX2_SENSE_ADJH_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != + ssCompData.header.sense_node - 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_IX2_SENSE_ADJH_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdj(adjhor, 1, + ssCompData.header.sense_node - 1, + thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMapAdj SS IX2 SENSE ADJH failed... ERROR COUNT = %d\n", + ret); + pr_err("SS IX2 SENSE ADJH TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS IX2 SENSE ADJH TEST:.................OK\n\n"); + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(adjhor); + adjhor = NULL; + } else + pr_info("SS IX2 SENSE ADJ TEST:.................SKIPPED\n"); + + /* SS TOTAL IX SENSE */ + pr_info("SS TOTAL IX SENSE TEST:\n"); + if (todo->SelfSenseIxTotal == 1 || todo->SelfSenseIxTotalAdj == 1) { + pr_info("SS TOTAL IX SENSE MIN MAX TEST:\n"); + if (todo->SelfSenseIxTotal == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_TOTAL_IX_SENSE_MAP_MIN, + &thresholds_min, + &trows, &tcolumns); + /* load the min thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != + totCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits SS_TOTAL_IX_SENSE_MAP_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_TOTAL_IX_SENSE_MAP_MAX, + &thresholds_max, + &trows, &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != + totCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits SS_TOTAL_IX_SENSE_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapTotalFromU(totCompData.ix_sn, 1, + totCompData.header.sense_node, + thresholds_min, + thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap SS TOTAL IX SENSE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS TOTAL IX SENSE MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS TOTAL IX SENSE MIN MAX TEST:.................OK\n\n"); + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else + pr_info("SS TOTAL IX SENSE MIN MAX TEST:.................SKIPPED\n"); + + + pr_info("SS TOTAL IX SENSE ADJ TEST:\n"); + if (todo->SelfSenseIxTotalAdj == 1) { + /* SS TOTAL IX SENSE ADJH TEST */ + pr_info("SS TOTAL IX SENSE ADJHORIZ TEST:\n"); + ret = computeAdjHorizTotalFromU(totCompData.ix_sn, 1, + totCompData.header.sense_node, + &total_adjhor); + if (ret < 0) { + pr_err("production_test_data: computeAdjHoriz SS TOTAL IX SENSE ADJH failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + pr_info("SS TOTAL IX SENSE ADJ HORIZ computed!\n"); + + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_TOTAL_IX_SENSE_ADJH_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != + totCompData.header.sense_node - 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_TOTAL_IX_SENSE_ADJH_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdjTotal(total_adjhor, 1, + totCompData.header.sense_node - 1, + thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMapAdj SS TOTAL IX SENSE ADJH failed... ERROR COUNT = %d\n", + ret); + pr_err("SS TOTAL IX SENSE ADJH TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS TOTAL IX SENSE ADJH TEST:.................OK\n\n"); + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(total_adjhor); + total_adjhor = NULL; + } else + pr_info("SS TOTAL IX SENSE ADJ TEST:.................SKIPPED\n"); + } else + pr_info("SS TOTAL IX SENSE TEST:.................SKIPPED\n"); + + /************* SS SENSE CX **************/ + /* SS CX1 FORCE TEST */ + pr_info("SS CX1 FORCE TEST:\n"); + if (todo->SelfForceCx1 == 1) { + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_CX1_FORCE_MIN_MAX, + &thresholds, &trows, &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + pr_err("production_test_data: parseProductionTestLimits SS_CX1_FORCE_MIN_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + container = (short)ssCompData.f_cx1; + ret = checkLimitsMinMax(&container, 1, 1, thresholds[0], + thresholds[1]); + /* check the limits */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMinMax SS CX1 FORCE TEST failed... ERROR COUNT = %d\n", + ret); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS CX1 FORCE TEST:.................OK\n\n"); + kfree(thresholds); + thresholds = NULL; + } else + pr_info("SS CX1 FORCE TEST:.................SKIPPED\n\n"); + + /* SS CX2 FORCE TEST */ + pr_info("SS CX2 FORCE MIN MAX TEST:\n"); + if (todo->SelfForceCx2 == 1) { + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_CX2_FORCE_MAP_MIN, + &thresholds_min, &trows, + &tcolumns); + /* load the min thresholds */ + if (ret < 0 || (trows != ssCompData.header.force_node || + tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_CX2_FORCE_MAP_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_CX2_FORCE_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != ssCompData.header.force_node || + tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_CX2_FORCE_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMap(ssCompData.cx2_fm, + ssCompData.header.force_node, 1, + thresholds_min, + thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap SS CX2 FORCE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS CX2 FORCE MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS CX2 FORCE MIN MAX TEST:.................OK\n\n"); + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else + pr_info("SS CX2 FORCE MIN MAX TEST:.................SKIPPED\n"); + + pr_info("SS CX2 FORCE ADJ TEST:\n"); + if (todo->SelfForceCx2Adj == 1) { + /* SS CX2 FORCE ADJV TEST */ + pr_info("SS CX2 FORCE ADJVERT TEST:\n"); + ret = computeAdjVert(ssCompData.cx2_fm, + ssCompData.header.force_node, 1, &adjvert); + /* compute the ADJV for CX2 FORCE */ + if (ret < 0) { + pr_err("production_test_data: computeAdjVert SS CX2 FORCE ADJV failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + pr_info("SS CX2 FORCE ADJV computed!\n"); + + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_CX2_FORCE_ADJV_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != ssCompData.header.force_node - 1 || + tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_CX2_FORCE_ADJV_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdj(adjvert, ssCompData.header.force_node - + 1, 1, thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap SS IX2 FORCE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS CX2 FORCE ADJV TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS CX2 FORCE ADJV TEST:.................OK\n\n"); + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(adjvert); + adjvert = NULL; + } else + pr_info("SS CX2 FORCE ADJ TEST:.................SKIPPED\n\n"); + + /* SS TOTAL CX FORCE */ + pr_info("SS TOTAL CX FORCE TEST:\n"); + if (todo->SelfForceCxTotal == 1 || todo->SelfForceCxTotalAdj == 1) { + pr_info("SS TOTAL CX FORCE MIN MAX TEST:\n"); + if (todo->SelfForceCxTotal == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_TOTAL_CX_FORCE_MAP_MIN, + &thresholds_min, + &trows, &tcolumns); + /* load the min thresholds */ + if (ret < 0 || (trows != + totCompData.header.force_node || + tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_TOTAL_CX_FORCE_MAP_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_TOTAL_CX_FORCE_MAP_MAX, + &thresholds_max, + &trows, &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != + totCompData.header.force_node || + tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_TOTAL_CX_FORCE_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapTotal(totCompData.cx_fm, + totCompData.header.force_node, + 1, thresholds_min, + thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap SS TOTAL FORCE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS TOTAL FORCE MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS TOTAL FORCE MIN MAX TEST:.................OK\n\n"); + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else + pr_info("SS TOTAL CX FORCE MIN MAX TEST:.................SKIPPED\n"); + + /* SS TOTAL CX FORCE ADJV TEST */ + pr_info("SS TOTAL CX FORCE ADJ TEST:\n"); + if (todo->SelfForceCxTotalAdj == 1) { + pr_info("SS TOTAL CX FORCE ADJVERT TEST:\n"); + ret = computeAdjVertTotal(totCompData.cx_fm, + totCompData.header.force_node, + 1, &total_adjvert); + /* compute the ADJV for CX2 FORCE */ + if (ret < 0) { + pr_err("production_test_data: computeAdjVert SS TOTAL CX FORCE ADJV failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + pr_info("SS TOTAL CX FORCE ADJV computed!\n"); + + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_TOTAL_CX_FORCE_ADJV_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != totCompData.header.force_node - + 1 || tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_TOTAL_CX_FORCE_ADJV_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdjTotal(total_adjvert, + totCompData.header.force_node - 1, 1, + thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap SS TOTAL CX FORCE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS TOTAL CX FORCE ADJV TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS TOTAL CX FORCE ADJV TEST:.................OK\n\n"); + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(total_adjvert); + total_adjvert = NULL; + } else + pr_info("SS TOTAL CX FORCE ADJ TEST:.................SKIPPED\n"); + } else + pr_info("SS TOTAL CX FORCE TEST:.................SKIPPED\n\n"); + + + + /************* SS SENSE CX *************/ + /* SS CX1 SENSE TEST */ + pr_info("SS CX1 SENSE TEST:\n"); + if (todo->SelfSenseCx1 == 1) { + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_CX1_SENSE_MIN_MAX, + &thresholds, &trows, &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + pr_err("production_test_data: parseProductionTestLimits SS_CX1_SENSE_MIN_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + container = (short)ssCompData.s_cx1; + ret = checkLimitsMinMax(&container, 1, 1, thresholds[0], + thresholds[1]); + /* check the limits */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMinMax SS CX1 SENSE TEST failed... ERROR COUNT = %d\n", + ret); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS CX1 SENSE TEST:.................OK\n\n"); + + kfree(thresholds); + thresholds = NULL; + } else + pr_info("SS CX1 SENSE TEST:.................SKIPPED\n\n"); + + + /* SS CX2 SENSE TEST */ + pr_info("SS CX2 SENSE MIN MAX TEST:\n"); + if (todo->SelfSenseCx2 == 1) { + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_CX2_SENSE_MAP_MIN, + &thresholds_min, &trows, + &tcolumns); + /* load the min thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != + ssCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits SS_CX2_SENSE_MAP_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_CX2_SENSE_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != + ssCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits SS_CX2_SENSE_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMap(ssCompData.cx2_sn, 1, + ssCompData.header.sense_node, + thresholds_min, thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap SS CX2 SENSE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS CX2 SENSE MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS CX2 SENSE MIN MAX TEST:.................OK\n\n"); + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else + pr_info("SS CX2 SENSE MIN MAX TEST:.................SKIPPED\n"); + + pr_info("SS CX2 SENSE ADJ TEST:\n"); + if (todo->SelfSenseCx2Adj == 1) { + /* SS CX2 SENSE ADJH TEST */ + pr_info("SS CX2 SENSE ADJHORIZ TEST:\n"); + ret = computeAdjHoriz(ssCompData.cx2_sn, 1, + ssCompData.header.sense_node, &adjhor); + if (ret < 0) { + pr_err("production_test_data: computeAdjHoriz SS CX2 SENSE ADJH failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + pr_info("SS CX2 SENSE ADJH computed!\n"); + + + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_CX2_SENSE_ADJH_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != + ssCompData.header.sense_node - 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_IX2_SENSE_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdj(adjhor, 1, + ssCompData.header.sense_node - 1, + thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMapAdj SS CX2 SENSE ADJH failed... ERROR COUNT = %d\n", + ret); + pr_err("SS CX2 SENSE ADJH TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS CX2 SENSE ADJH TEST:.................OK\n"); + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(adjhor); + adjhor = NULL; + } else + pr_info("SS CX2 SENSE ADJ TEST:.................SKIPPED\n\n"); + + /* SS TOTAL CX SENSE */ + pr_info("SS TOTAL CX SENSE TEST:\n"); + if (todo->SelfSenseCxTotal == 1 || todo->SelfSenseCxTotalAdj == 1) { + pr_info("SS TOTAL CX SENSE MIN MAX TEST:\n"); + if (todo->SelfSenseCxTotal == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_TOTAL_CX_SENSE_MAP_MIN, + &thresholds_min, + &trows, &tcolumns); + /* load the min thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != + totCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits SS_TOTAL_CX_SENSE_MAP_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_TOTAL_CX_SENSE_MAP_MAX, + &thresholds_max, + &trows, &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != + totCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits SS_TOTAL_CX_SENSE_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapTotal(totCompData.cx_sn, 1, + totCompData.header.sense_node, + thresholds_min, + thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap SS TOTAL CX SENSE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS TOTAL CX SENSE MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS TOTAL CX SENSE MIN MAX TEST:.................OK\n\n"); + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else + pr_info("SS TOTAL CX SENSE MIN MAX TEST:.................SKIPPED\n"); + + + /* SS TOTAL IX SENSE ADJH TEST */ + pr_info("SS TOTAL CX SENSE ADJ TEST:\n"); + if (todo->SelfSenseCxTotalAdj == 1) { + pr_info("SS TOTAL CX SENSE ADJHORIZ TEST:\n"); + ret = computeAdjHorizTotal(totCompData.cx_sn, 1, + totCompData.header.sense_node, + &total_adjhor); + if (ret < 0) { + pr_err("production_test_data: computeAdjHoriz SS TOTAL CX SENSE ADJH failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + pr_info("SS TOTAL CX SENSE ADJ HORIZ computed!\n"); + + + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_TOTAL_CX_SENSE_ADJH_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != + totCompData.header.sense_node - 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_TOTAL_CX_SENSE_ADJH_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdjTotal(total_adjhor, 1, + totCompData.header.sense_node - 1, + thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMapAdj SS TOTAL CX SENSE ADJH failed... ERROR COUNT = %d\n", + ret); + pr_err("SS TOTAL CX SENSE ADJH TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS TOTAL CX SENSE ADJH TEST:.................OK\n\n"); + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(total_adjhor); + total_adjhor = NULL; + } else + pr_info("SS TOTAL CX SENSE ADJ TEST:.................SKIPPED\n"); + } else + pr_info("SS TOTAL CX SENSE TEST:.................SKIPPED\n"); + + if ((todo->SelfSenseCx1LP | todo->SelfSenseCx2LP | + todo->SelfSenseCx2AdjLP | todo->SelfSenseCxTotalLP | + todo->SelfSenseCxTotalAdjLP | todo->SelfSenseIx1LP | + todo->SelfSenseIx2LP | todo->SelfSenseIx2AdjLP | + todo->SelfSenseIxTotalLP | todo->SelfSenseIxTotalAdjLP) == 1) { + ret = production_test_ss_ix_cx_lp(path_limits, stop_on_fail, + todo); + if (ret < OK) { + count_fail += 1; + pr_err("production_test_data: production_test_ss_ix_cx_lp failed... ERROR = %08X\n", + ret); + goto ERROR; + } + } else + pr_info("SS IX CX LP TEST:.................SKIPPED\n"); + +ERROR: + if (count_fail == 0) { + kfree(ssCompData.ix2_fm); + ssCompData.ix2_fm = NULL; + kfree(ssCompData.ix2_sn); + ssCompData.ix2_sn = NULL; + kfree(ssCompData.cx2_fm); + ssCompData.cx2_fm = NULL; + kfree(ssCompData.cx2_sn); + ssCompData.cx2_sn = NULL; + kfree(totCompData.ix_fm); + totCompData.ix_fm = NULL; + kfree(totCompData.ix_sn); + totCompData.ix_sn = NULL; + kfree(totCompData.cx_fm); + totCompData.cx_fm = NULL; + kfree(totCompData.cx_sn); + totCompData.cx_sn = NULL; + pr_info("SS IX CX testes finished!.................OK\n\n"); + return OK; + } + + /* print all kind of data in just one row for readability reason */ + print_frame_u8("SS Init Data Ix2_fm = ", array1dTo2d_u8( + ssCompData.ix2_fm, + ssCompData.header.force_node, 1), + ssCompData.header.force_node, 1); + print_frame_i8("SS Init Data Cx2_fm = ", array1dTo2d_i8( + ssCompData.cx2_fm, + ssCompData.header.force_node, 1), + ssCompData.header.force_node, 1); + print_frame_u8("SS Init Data Ix2_sn = ", array1dTo2d_u8( + ssCompData.ix2_sn, + ssCompData.header.sense_node, + ssCompData.header.sense_node), 1, + ssCompData.header.sense_node); + print_frame_i8("SS Init Data Cx2_sn = ", array1dTo2d_i8( + ssCompData.cx2_sn, + ssCompData.header.sense_node, + ssCompData.header.sense_node), 1, + ssCompData.header.sense_node); + print_frame_u16("TOT SS Init Data Ix_fm = ", array1dTo2d_u16( + totCompData.ix_fm, + totCompData.header.force_node, 1), + totCompData.header.force_node, 1); + print_frame_short("TOT SS Init Data Cx_fm = ", + array1dTo2d_short(totCompData.cx_fm, + totCompData.header.force_node, + 1), + totCompData.header.force_node, 1); + print_frame_u16("TOT SS Init Data Ix_sn = ", array1dTo2d_u16( + totCompData.ix_sn, + totCompData.header.sense_node, + totCompData.header.sense_node), 1, + totCompData.header.sense_node); + print_frame_short("TOT SS Init Data Cx_sn = ", + array1dTo2d_short(totCompData.cx_sn, + totCompData.header.sense_node, + totCompData.header.sense_node), + 1, totCompData.header.sense_node); + pr_err("SS IX CX testes finished!.................FAILED fails_count = %d\n\n", + count_fail); + if (thresholds != NULL) + kfree(thresholds); + if (thresholds_min != NULL) + kfree(thresholds_min); + if (thresholds_max != NULL) + kfree(thresholds_max); + if (adjhor != NULL) + kfree(adjhor); + if (adjvert != NULL) + kfree(adjvert); + if (total_adjhor != NULL) + kfree(total_adjhor); + if (total_adjvert != NULL) + kfree(total_adjvert); + if (ssCompData.ix2_fm != NULL) + kfree(ssCompData.ix2_fm); + if (ssCompData.ix2_sn != NULL) + kfree(ssCompData.ix2_sn); + if (ssCompData.cx2_fm != NULL) + kfree(ssCompData.cx2_fm); + if (ssCompData.cx2_sn != NULL) + kfree(ssCompData.cx2_sn); + if (totCompData.ix_fm != NULL) + kfree(totCompData.ix_fm); + if (totCompData.ix_sn != NULL) + kfree(totCompData.ix_sn); + if (totCompData.cx_fm != NULL) + kfree(totCompData.cx_fm); + if (totCompData.cx_sn != NULL) + kfree(totCompData.cx_sn); + return ERROR_TEST_CHECK_FAIL | ERROR_PROD_TEST_DATA; + + +ERROR_LIMITS: + if (thresholds != NULL) + kfree(thresholds); + if (thresholds_min != NULL) + kfree(thresholds_min); + if (thresholds_max != NULL) + kfree(thresholds_max); + if (adjhor != NULL) + kfree(adjhor); + if (adjvert != NULL) + kfree(adjvert); + if (total_adjhor != NULL) + kfree(total_adjhor); + if (total_adjvert != NULL) + kfree(total_adjvert); + if (ssCompData.ix2_fm != NULL) + kfree(ssCompData.ix2_fm); + if (ssCompData.ix2_sn != NULL) + kfree(ssCompData.ix2_sn); + if (ssCompData.cx2_fm != NULL) + kfree(ssCompData.cx2_fm); + if (ssCompData.cx2_sn != NULL) + kfree(ssCompData.cx2_sn); + if (totCompData.ix_fm != NULL) + kfree(totCompData.ix_fm); + if (totCompData.ix_sn != NULL) + kfree(totCompData.ix_sn); + if (totCompData.cx_fm != NULL) + kfree(totCompData.cx_fm); + if (totCompData.cx_sn != NULL) + kfree(totCompData.cx_sn); + return ret; +} + +/** + * Perform all the tests selected in a TestTodo variable related to SS Init + * data for LP mode (touch, keys etc..) + * @param path_limits name of Production Limit file to load or + * "NULL" if the limits data should be loaded by a .h file + * @param stop_on_fail if 1, the test flow stops at the first data check + * failure + * otherwise it keeps going performing all the selected test + * @param todo pointer to a TestToDo variable which select the test to do + * @return OK if success or an error code which specify the type of error + */ +int production_test_ss_ix_cx_lp(const char *path_limits, int stop_on_fail, + TestToDo *todo) +{ + int ret; + int count_fail = 0; + + int *thresholds = NULL; + int trows, tcolumns; + int *thresholds_min = NULL; + int *thresholds_max = NULL; + + SelfSenseData ssCompData; + TotSelfSenseData totCompData; + + u8 *adjhor = NULL; + u8 *adjvert = NULL; + + short container; + + u16 *total_adjhor = NULL; + u16 *total_adjvert = NULL; + + pr_info("SS LP IX CX testes are starting...\n"); + ret = readSelfSenseCompensationData(LOAD_CX_SS_TOUCH_IDLE, &ssCompData); + /* read the SS LP compensation data */ + if (ret < 0) { + pr_err("production_test_data: readSelfSenseCompensationData failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + return ret | ERROR_PROD_TEST_DATA; + } + + ret = readTotSelfSenseCompensationData(LOAD_PANEL_CX_TOT_SS_TOUCH_IDLE, + &totCompData); + /* read the TOT SS LP compensation data */ + if (ret < 0) { + pr_err("production_test_data: readTotSelfSenseCompensationData failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + kfree(ssCompData.ix2_fm); + kfree(ssCompData.ix2_sn); + kfree(ssCompData.cx2_fm); + kfree(ssCompData.cx2_sn); + return ret | ERROR_PROD_TEST_DATA; + } + + /************* SS FORCE IX LP **************/ + /* SS IX1 LP FORCE TEST */ + pr_info("SS IX1 LP FORCE TEST:\n"); + if (todo->SelfForceIx1LP == 1) { + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_IX1_LP_FORCE_MIN_MAX, + &thresholds, &trows, &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + pr_err("production_test_data: parseProductionTestLimits SS_IX1_LP_FORCE_MIN_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + container = (short)ssCompData.f_ix1; + ret = checkLimitsMinMax(&container, 1, 1, thresholds[0], + thresholds[1]); + /* check the limits */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMinMax SS IX1 LP FORCE TEST failed... ERROR COUNT = %d\n", + ret); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS IX1 LP FORCE TEST:.................OK\n\n"); + } else + pr_info("SS IX1 LP FORCE TEST:.................SKIPPED\n\n"); + + kfree(thresholds); + thresholds = NULL; + /* SS IX2 LP FORCE TEST */ + pr_info("SS IX2 LP FORCE MIN MAX TEST:\n"); + if (todo->SelfForceIx2LP == 1) { + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_IX2_LP_FORCE_MAP_MIN, + &thresholds_min, &trows, + &tcolumns); + /* load the min thresholds */ + if (ret < 0 || (trows != ssCompData.header.force_node || + tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_IX2_LP_FORCE_MAP_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_IX2_LP_FORCE_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != ssCompData.header.force_node || + tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_IX2_LP_FORCE_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapFromU(ssCompData.ix2_fm, + ssCompData.header.force_node, 1, + thresholds_min, + thresholds_max); /* check the + * values with + * thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap SS IX2 LP FORCE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS IX2 LP FORCE MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS IX2 LP FORCE MIN MAX TEST:.................OK\n\n"); + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else + pr_info("SS IX2 LP FORCE MIN MAX TEST:.................SKIPPED\n\n"); + + pr_info("SS IX2 LP FORCE ADJ TEST:\n"); + if (todo->SelfForceIx2AdjLP == 1) { + /* SS IX2 FORCE ADJV TEST */ + pr_info("SS IX2 LP FORCE ADJVERT TEST:\n"); + ret = computeAdjVertFromU(ssCompData.ix2_fm, + ssCompData.header.force_node, 1, + &adjvert); + if (ret < 0) { + pr_err("production_test_data: computeAdjVert SS IX2 LP FORCE ADJV failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + pr_info("SS IX2 LP FORCE ADJV computed!\n"); + + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_IX2_LP_FORCE_ADJV_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); /* load the max + * thresholds */ + if (ret < 0 || (trows != ssCompData.header.force_node - 1 || + tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_IX2_LP_FORCE_ADJV_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdj(adjvert, ssCompData.header.force_node - + 1, 1, thresholds_max); /* check the + * values with + * thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap SS IX2 LP FORCE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS IX2 LP FORCE ADJV TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS IX2 LP FORCE ADJV TEST:.................OK\n\n"); + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(adjvert); + adjvert = NULL; + } else + pr_info("SS IX2 LP FORCE ADJ TEST:.................SKIPPED\n\n"); + + /* SS TOTAL FORCE IX */ + pr_info("SS TOTAL IX LP FORCE TEST:\n"); + if (todo->SelfForceIxTotalLP == 1 || todo->SelfForceIxTotalAdjLP == 1) { + pr_info("SS TOTAL IX LP FORCE MIN MAX TEST:\n"); + if (todo->SelfForceIxTotalLP == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_TOTAL_IX_LP_FORCE_MAP_MIN, + &thresholds_min, + &trows, &tcolumns); + /* load the min thresholds */ + if (ret < 0 || (trows != + totCompData.header.force_node || + tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_TOTAL_IX_LP_FORCE_MAP_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_TOTAL_IX_LP_FORCE_MAP_MAX, + &thresholds_max, + &trows, &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != + totCompData.header.force_node || + tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_TOTAL_IX_LP_FORCE_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapTotalFromU(totCompData.ix_fm, + totCompData.header. + force_node, 1, + thresholds_min, + thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap SS TOTAL IX LP FORCE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS TOTAL IX LP FORCE MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS TOTAL IX LP FORCE MIN MAX TEST:.................OK\n\n"); + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else + pr_info("SS TOTAL IX LP FORCE MIN MAX TEST:.................SKIPPED\n"); + + pr_info("SS TOTAL IX LP FORCE ADJ TEST:\n"); + if (todo->SelfForceIxTotalAdjLP == 1) { + /* SS TOTAL IX FORCE ADJV TEST */ + pr_info("SS TOTAL IX LP FORCE ADJVERT TEST:\n"); + ret = computeAdjVertTotalFromU(totCompData.ix_fm, + totCompData.header. + force_node, 1, + &total_adjvert); + if (ret < 0) { + pr_err("production_test_data: computeAdjVert SS TOTAL IX LP FORCE ADJV failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + pr_info("SS TOTAL IX LP FORCE ADJV computed!\n"); + + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_TOTAL_IX_LP_FORCE_ADJV_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != totCompData.header.force_node - + 1 || tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_TOTAL_IX_LP_FORCE_ADJV_MAP_MAX... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdjTotal(total_adjvert, + totCompData.header. + force_node - 1, 1, + thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap SS TOTAL IX LP FORCE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS TOTAL IX LP FORCE ADJV TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS TOTAL IX LP FORCE ADJV TEST:.................OK\n\n"); + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(total_adjvert); + total_adjvert = NULL; + } else + pr_info("SS TOTAL IX LP FORCE ADJ TEST:.................SKIPPED\n"); + } else + pr_info("SS TOTAL IX LP FORCE TEST:.................SKIPPED\n\n"); + + + /************** SS SENSE IX LP **************/ + /* SS IX1 LP SENSE TEST */ + pr_info("SS IX1 LP SENSE TEST:\n"); + if (todo->SelfSenseIx1LP == 1) { + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_IX1_LP_SENSE_MIN_MAX, + &thresholds, &trows, &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + pr_err("production_test_data: parseProductionTestLimits SS_IX1_LP_SENSE_MIN_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + container = (short)ssCompData.s_ix1; + ret = checkLimitsMinMax(&container, 1, 1, thresholds[0], + thresholds[1]); + /* check the limits */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMinMax SS IX1 SENSE LP TEST failed... ERROR COUNT = %d\n", + ret); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS IX1 LP SENSE TEST:.................OK\n\n"); + } else + pr_info("SS IX1 LP SENSE TEST:.................SKIPPED\n\n"); + + kfree(thresholds); + thresholds = NULL; + /* SS IX2 SENSE TEST */ + pr_info("SS IX2 LP SENSE MIN MAX TEST:\n"); + if (todo->SelfSenseIx2LP == 1) { + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_IX2_LP_SENSE_MAP_MIN, + &thresholds_min, &trows, + &tcolumns); + /* load the min thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != + ssCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits SS_IX2_LP_SENSE_MAP_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_IX2_LP_SENSE_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != + ssCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits SS_IX2_LP_SENSE_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapFromU(ssCompData.ix2_sn, 1, + ssCompData.header.sense_node, + thresholds_min, thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap SS IX2 LP SENSE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS IX2 LP SENSE MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS IX2 LP SENSE MIN MAX TEST:.................OK\n\n"); + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else + pr_info("SS IX2 LP SENSE MIN MAX TEST:.................SKIPPED\n\n"); + + pr_info("SS IX2 LP SENSE ADJ TEST:\n"); + if (todo->SelfSenseIx2AdjLP == 1) { + /* SS IX2 SENSE ADJH TEST */ + pr_info("SS IX2 SENSE ADJHORIZ TEST:\n"); + ret = computeAdjHorizFromU(ssCompData.ix2_sn, 1, + ssCompData.header.sense_node, + &adjhor); + if (ret < 0) { + pr_err("production_test_data: computeAdjHoriz SS IX2 SENSE ADJH failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + pr_info("SS IX2 SENSE ADJ HORIZ computed!\n"); + + + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_IX2_SENSE_ADJH_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != + ssCompData.header.sense_node - 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_IX2_LP_SENSE_ADJH_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdj(adjhor, 1, + ssCompData.header.sense_node - 1, + thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMapAdj SS IX2 LP SENSE ADJH failed... ERROR COUNT = %d\n", + ret); + pr_err("SS IX2 LP SENSE ADJH TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS IX2 LP SENSE ADJH TEST:.................OK\n\n"); + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(adjhor); + adjhor = NULL; + } else + pr_info("SS IX2 LP SENSE ADJ TEST:.................SKIPPED\n"); + + /* SS TOTAL IX SENSE */ + pr_info("SS TOTAL IX LP SENSE TEST:\n"); + if (todo->SelfSenseIxTotalLP == 1 || todo->SelfSenseIxTotalAdjLP == 1) { + pr_info("SS TOTAL IX LP SENSE MIN MAX TEST:\n"); + if (todo->SelfSenseIxTotalLP == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_TOTAL_IX_LP_SENSE_MAP_MIN, + &thresholds_min, + &trows, &tcolumns); + /* load the min thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != + totCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits SS_TOTAL_IX_LP_SENSE_MAP_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_TOTAL_IX_LP_SENSE_MAP_MAX, + &thresholds_max, + &trows, &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != + totCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits SS_TOTAL_IX_LP_SENSE_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapTotalFromU(totCompData.ix_sn, 1, + totCompData.header. + sense_node, + thresholds_min, + thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap SS TOTAL IX LP SENSE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS TOTAL IX LP SENSE MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS TOTAL IX LP SENSE MIN MAX TEST:.................OK\n\n"); + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else + pr_info("SS TOTAL IX LP SENSE MIN MAX TEST:.................SKIPPED\n"); + + + pr_info("SS TOTAL IX LP SENSE ADJ TEST:\n"); + if (todo->SelfSenseIxTotalAdjLP == 1) { + /* SS TOTAL IX SENSE ADJH TEST */ + pr_info("SS TOTAL IX LP SENSE ADJHORIZ TEST:\n"); + ret = computeAdjHorizTotalFromU(totCompData.ix_sn, 1, + totCompData.header. + sense_node, + &total_adjhor); + if (ret < 0) { + pr_err("production_test_data: computeAdjHoriz SS TOTAL IX LP SENSE ADJH failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + pr_info("SS TOTAL IX LP SENSE ADJ HORIZ computed!\n"); + + + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_TOTAL_IX_LP_SENSE_ADJH_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != + totCompData.header.sense_node - 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_TOTAL_IX_LP_SENSE_ADJH_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdjTotal(total_adjhor, 1, + totCompData.header. + sense_node - 1, + thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMapAdj SS TOTAL IX LP SENSE ADJH failed... ERROR COUNT = %d\n", + ret); + pr_err("SS TOTAL IX LP SENSE ADJH TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS TOTAL IX LP SENSE ADJH TEST:.................OK\n\n"); + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(total_adjhor); + total_adjhor = NULL; + } else + pr_info("SS TOTAL IX LP SENSE ADJ TEST:.................SKIPPED\n"); + } else + pr_info("SS TOTAL IX LP SENSE TEST:.................SKIPPED\n"); + + /************* SS SENSE CX LP **************/ + /* SS CX1 LP FORCE TEST */ + pr_info("SS CX1 LP FORCE TEST:\n"); + if (todo->SelfForceCx1LP == 1) { + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_CX1_LP_FORCE_MIN_MAX, + &thresholds, &trows, &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + pr_err("production_test_data: parseProductionTestLimits SS_CX1_LP_FORCE_MIN_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + container = (short)ssCompData.f_cx1; + ret = checkLimitsMinMax(&container, 1, 1, thresholds[0], + thresholds[1]); + /* check the limits */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMinMax SS CX1 LP FORCE TEST failed... ERROR COUNT = %d\n", + ret); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS CX1 LP FORCE TEST:.................OK\n\n"); + kfree(thresholds); + thresholds = NULL; + } else + pr_info("SS CX1 LP FORCE TEST:.................SKIPPED\n\n"); + + + + /* SS CX2 LP FORCE TEST */ + pr_info("SS CX2 LP FORCE MIN MAX TEST:\n"); + if (todo->SelfForceCx2LP == 1) { + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_CX2_LP_FORCE_MAP_MIN, + &thresholds_min, &trows, + &tcolumns); + /* load the min thresholds */ + if (ret < 0 || (trows != ssCompData.header.force_node || + tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_CX2_LP_FORCE_MAP_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_CX2_LP_FORCE_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != ssCompData.header.force_node || + tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_CX2_LP_FORCE_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMap(ssCompData.cx2_fm, + ssCompData.header.force_node, 1, + thresholds_min, + thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap SS CX2 LP FORCE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS CX2 LP FORCE MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS CX2 LP FORCE MIN MAX TEST:.................OK\n\n"); + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else + pr_info("SS CX2 LP FORCE MIN MAX TEST:.................SKIPPED\n"); + + pr_info("SS CX2 LP FORCE ADJ TEST:\n"); + if (todo->SelfForceCx2AdjLP == 1) { + /* SS CX2 FORCE ADJV TEST */ + pr_info("SS CX2 LP FORCE ADJVERT TEST:\n"); + ret = computeAdjVert(ssCompData.cx2_fm, + ssCompData.header.force_node, 1, &adjvert); + /* compute the ADJV for CX2 FORCE */ + if (ret < 0) { + pr_err("production_test_data: computeAdjVert SS CX2 LP FORCE ADJV failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + pr_info("SS CX2 LP FORCE ADJV computed!\n"); + + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_CX2_LP_FORCE_ADJV_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != ssCompData.header.force_node - 1 || + tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_CX2_LP_FORCE_ADJV_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdj(adjvert, ssCompData.header.force_node - + 1, 1, thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap SS IX2 LP FORCE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS CX2 LP FORCE ADJV TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS CX2 LP FORCE ADJV TEST:.................OK\n\n"); + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(adjvert); + adjvert = NULL; + } else + pr_info("SS CX2 LP FORCE ADJ TEST:.................SKIPPED\n\n"); + + /* SS TOTAL CX LP FORCE */ + pr_info("SS TOTAL CX LP FORCE TEST:\n"); + if (todo->SelfForceCxTotalLP == 1 || todo->SelfForceCxTotalAdjLP == 1) { + pr_info("SS TOTAL CX LP FORCE MIN MAX TEST:\n"); + if (todo->SelfForceCxTotalLP == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_TOTAL_CX_LP_FORCE_MAP_MIN, + &thresholds_min, + &trows, &tcolumns); + /* load the min thresholds */ + if (ret < 0 || (trows != + totCompData.header.force_node || + tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_TOTAL_CX_LP_FORCE_MAP_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_TOTAL_CX_LP_FORCE_MAP_MAX, + &thresholds_max, + &trows, &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != + totCompData.header.force_node || + tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_TOTAL_CX_LP_FORCE_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapTotal(totCompData.cx_fm, + totCompData.header.force_node, + 1, thresholds_min, + thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap SS TOTAL LP FORCE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS TOTAL FORCE LP MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS TOTAL FORCE LP MIN MAX TEST:.................OK\n\n"); + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else + pr_info("SS TOTAL CX LP FORCE MIN MAX TEST:.................SKIPPED\n"); + + /* SS TOTAL CX LP FORCE ADJV TEST */ + pr_info("SS TOTAL CX LP FORCE ADJ TEST:\n"); + if (todo->SelfForceCxTotalAdjLP == 1) { + pr_info("SS TOTAL CX LP FORCE ADJVERT TEST:\n"); + ret = computeAdjVertTotal(totCompData.cx_fm, + totCompData.header.force_node, + 1, &total_adjvert); + /* compute the ADJV for CX2 FORCE */ + if (ret < 0) { + pr_err("production_test_data: computeAdjVert SS TOTAL CX LP FORCE ADJV failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + pr_info("SS TOTAL CX LP FORCE ADJV computed!\n"); + + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_TOTAL_CX_LP_FORCE_ADJV_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != totCompData.header.force_node - + 1 || tcolumns != 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_TOTAL_CX_LP_FORCE_ADJV_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdjTotal(total_adjvert, + totCompData.header. + force_node - 1, 1, + thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap SS TOTAL CX LP FORCE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS TOTAL CX LP FORCE ADJV TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS TOTAL CX LP FORCE ADJV TEST:.................OK\n\n"); + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(total_adjvert); + total_adjvert = NULL; + } else + pr_info("SS TOTAL CX LP FORCE ADJ TEST:.................SKIPPED\n"); + } else + pr_info("SS TOTAL CX LP FORCE TEST:.................SKIPPED\n\n"); + + + + /************* SS SENSE CX *************/ + /* SS CX1 SENSE TEST */ + pr_info("SS CX1 LP SENSE TEST:\n"); + if (todo->SelfSenseCx1LP == 1) { + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_CX1_LP_SENSE_MIN_MAX, + &thresholds, &trows, &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + pr_err("production_test_data: parseProductionTestLimits SS_CX1_LP_SENSE_MIN_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + container = (short)ssCompData.s_cx1; + ret = checkLimitsMinMax(&container, 1, 1, thresholds[0], + thresholds[1]); + /* check the limits */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMinMax SS CX1 LP SENSE TEST failed... ERROR COUNT = %d\n", + ret); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS CX1 LP SENSE TEST:.................OK\n\n"); + kfree(thresholds); + thresholds = NULL; + } else + pr_info("SS CX1 LP SENSE TEST:.................SKIPPED\n\n"); + + + /* SS CX2 LP SENSE TEST */ + pr_info("SS CX2 LP SENSE MIN MAX TEST:\n"); + if (todo->SelfSenseCx2LP == 1) { + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_CX2_LP_SENSE_MAP_MIN, + &thresholds_min, &trows, + &tcolumns); + /* load the min thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != + ssCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits SS_CX2_LP_SENSE_MAP_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_CX2_LP_SENSE_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != + ssCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits SS_CX2_LP_SENSE_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMap(ssCompData.cx2_sn, 1, + ssCompData.header.sense_node, + thresholds_min, thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap SS CX2 LP SENSE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS CX2 LP SENSE MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS CX2 LP SENSE MIN MAX TEST:.................OK\n\n"); + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else + pr_info("SS CX2 LP SENSE MIN MAX TEST:.................SKIPPED\n"); + + pr_info("SS CX2 LP SENSE ADJ TEST:\n"); + if (todo->SelfSenseCx2AdjLP == 1) { + /* SS CX2 SENSE ADJH TEST */ + pr_info("SS CX2 LP SENSE ADJHORIZ TEST:\n"); + ret = computeAdjHoriz(ssCompData.cx2_sn, 1, + ssCompData.header.sense_node, &adjhor); + if (ret < 0) { + pr_err("production_test_data: computeAdjHoriz SS CX2 LP SENSE ADJH failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + pr_info("SS CX2 LP SENSE ADJH computed!\n"); + + + ret = parseProductionTestLimits(path_limits, &limit_file, + SS_CX2_LP_SENSE_ADJH_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != + ssCompData.header.sense_node - 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_IX2_SENSE_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdj(adjhor, 1, + ssCompData.header.sense_node - 1, + thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMapAdj SS CX2 LP SENSE ADJH failed... ERROR COUNT = %d\n", + ret); + pr_err("SS CX2 LP SENSE ADJH TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS CX2 LP SENSE ADJH TEST:.................OK\n"); + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(adjhor); + adjhor = NULL; + } else + pr_info("SS CX2 LP SENSE ADJ TEST:.................SKIPPED\n\n"); + + /* SS TOTAL CX SENSE */ + pr_info("SS TOTAL CX LP SENSE TEST:\n"); + if (todo->SelfSenseCxTotalLP == 1 || todo->SelfSenseCxTotalAdjLP == 1) { + pr_info("SS TOTAL CX LP SENSE MIN MAX TEST:\n"); + if (todo->SelfSenseCxTotalLP == 1) { + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_TOTAL_CX_LP_SENSE_MAP_MIN, + &thresholds_min, + &trows, &tcolumns); + /* load the min thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != + totCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits SS_TOTAL_CX_LP_SENSE_MAP_MIN failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_TOTAL_CX_LP_SENSE_MAP_MAX, + &thresholds_max, + &trows, &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != + totCompData.header.sense_node)) { + pr_err("production_test_data: parseProductionTestLimits SS_TOTAL_CX_LP_SENSE_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapTotal(totCompData.cx_sn, 1, + totCompData.header.sense_node, + thresholds_min, + thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMap SS TOTAL CX LP SENSE failed... ERROR COUNT = %d\n", + ret); + pr_err("SS TOTAL CX LP SENSE MIN MAX TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS TOTAL CX LP SENSE MIN MAX TEST:.................OK\n\n"); + + kfree(thresholds_min); + thresholds_min = NULL; + kfree(thresholds_max); + thresholds_max = NULL; + } else + pr_info("SS TOTAL CX LP SENSE MIN MAX TEST:.................SKIPPED\n"); + + + /* SS TOTAL IX SENSE ADJH TEST */ + pr_info("SS TOTAL CX LP SENSE ADJ TEST:\n"); + if (todo->SelfSenseCxTotalAdjLP == 1) { + pr_info("SS TOTAL CX LP SENSE ADJHORIZ TEST:\n"); + ret = computeAdjHorizTotal(totCompData.cx_sn, 1, + totCompData.header.sense_node, + &total_adjhor); + if (ret < 0) { + pr_err("production_test_data: computeAdjHoriz SS TOTAL CX LP SENSE ADJH failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + pr_info("SS TOTAL CX LP SENSE ADJ HORIZ computed!\n"); + + + ret = parseProductionTestLimits(path_limits, + &limit_file, + SS_TOTAL_CX_LP_SENSE_ADJH_MAP_MAX, + &thresholds_max, &trows, + &tcolumns); + /* load the max thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != + totCompData.header.sense_node - 1)) { + pr_err("production_test_data: parseProductionTestLimits SS_TOTAL_CX_LP_SENSE_ADJH_MAP_MAX failed... ERROR %08X\n", + ERROR_PROD_TEST_DATA); + ret |= ERROR_PROD_TEST_DATA; + goto ERROR_LIMITS; + } + + ret = checkLimitsMapAdjTotal(total_adjhor, 1, + totCompData.header. + sense_node - 1, + thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + pr_err("production_test_data: checkLimitsMapAdj SS TOTAL CX LP SENSE ADJH failed... ERROR COUNT = %d\n", + ret); + pr_err("SS TOTAL CX LP SENSE ADJH TEST:.................FAIL\n\n"); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + pr_info("SS TOTAL CX LP SENSE ADJH TEST:.................OK\n\n"); + + kfree(thresholds_max); + thresholds_max = NULL; + kfree(total_adjhor); + total_adjhor = NULL; + } else + pr_info("SS TOTAL CX LP SENSE ADJ TEST:.................SKIPPED\n"); + } else + pr_info("SS TOTAL CX LP SENSE TEST:.................SKIPPED\n"); + + + +ERROR: + + if (count_fail == 0) { + kfree(ssCompData.ix2_fm); + ssCompData.ix2_fm = NULL; + kfree(ssCompData.ix2_sn); + ssCompData.ix2_sn = NULL; + kfree(ssCompData.cx2_fm); + ssCompData.cx2_fm = NULL; + kfree(ssCompData.cx2_sn); + ssCompData.cx2_sn = NULL; + kfree(totCompData.ix_fm); + totCompData.ix_fm = NULL; + kfree(totCompData.ix_sn); + totCompData.ix_sn = NULL; + kfree(totCompData.cx_fm); + totCompData.cx_fm = NULL; + kfree(totCompData.cx_sn); + totCompData.cx_sn = NULL; + pr_info("SS LP IX CX testes finished!.................OK\n\n"); + return OK; + } else { + /* print all kind of data in just one row for readability reason */ + print_frame_u8("SS LP Init Data Ix2_fm = ", array1dTo2d_u8( + ssCompData.ix2_fm, + ssCompData.header.force_node, 1), + ssCompData.header.force_node, 1); + print_frame_i8("SS LP Init Data Cx2_fm = ", array1dTo2d_i8( + ssCompData.cx2_fm, + ssCompData.header.force_node, 1), + ssCompData.header.force_node, 1); + print_frame_u8("SS LP Init Data Ix2_sn = ", array1dTo2d_u8( + ssCompData.ix2_sn, + ssCompData.header.sense_node, + ssCompData.header.sense_node), 1, + ssCompData.header.sense_node); + print_frame_i8("SS LP Init Data Cx2_sn = ", array1dTo2d_i8( + ssCompData.cx2_sn, + ssCompData.header.sense_node, + ssCompData.header.sense_node), 1, + ssCompData.header.sense_node); + print_frame_u16("TOT SS LP Init Data Ix_fm = ", array1dTo2d_u16( + totCompData.ix_fm, + totCompData.header.force_node, 1), + totCompData.header.force_node, 1); + print_frame_short("TOT SS LP Init Data Cx_fm = ", + array1dTo2d_short(totCompData.cx_fm, + totCompData.header. + force_node, 1), + totCompData.header.force_node, 1); + print_frame_u16("TOT SS LP Init Data Ix_sn = ", array1dTo2d_u16( + totCompData.ix_sn, + totCompData.header.sense_node, + totCompData.header.sense_node), 1, + totCompData.header.sense_node); + print_frame_short("TOT SS LP Init Data Cx_sn = ", + array1dTo2d_short(totCompData.cx_sn, + totCompData.header. + sense_node, + totCompData.header. + sense_node), + 1, totCompData.header.sense_node); + pr_err("SS LP IX CX testes finished!.................FAILED fails_count = %d\n\n", + count_fail); + if (thresholds != NULL) + kfree(thresholds); + if (thresholds_min != NULL) + kfree(thresholds_min); + if (thresholds_max != NULL) + kfree(thresholds_max); + if (adjhor != NULL) + kfree(adjhor); + if (adjvert != NULL) + kfree(adjvert); + if (total_adjhor != NULL) + kfree(total_adjhor); + if (total_adjvert != NULL) + kfree(total_adjvert); + if (ssCompData.ix2_fm != NULL) + kfree(ssCompData.ix2_fm); + if (ssCompData.ix2_sn != NULL) + kfree(ssCompData.ix2_sn); + if (ssCompData.cx2_fm != NULL) + kfree(ssCompData.cx2_fm); + if (ssCompData.cx2_sn != NULL) + kfree(ssCompData.cx2_sn); + if (totCompData.ix_fm != NULL) + kfree(totCompData.ix_fm); + if (totCompData.ix_sn != NULL) + kfree(totCompData.ix_sn); + if (totCompData.cx_fm != NULL) + kfree(totCompData.cx_fm); + if (totCompData.cx_sn != NULL) + kfree(totCompData.cx_sn); + return ERROR_TEST_CHECK_FAIL | ERROR_PROD_TEST_DATA; + } + +ERROR_LIMITS: + if (thresholds != NULL) + kfree(thresholds); + if (thresholds_min != NULL) + kfree(thresholds_min); + if (thresholds_max != NULL) + kfree(thresholds_max); + if (adjhor != NULL) + kfree(adjhor); + if (adjvert != NULL) + kfree(adjvert); + if (total_adjhor != NULL) + kfree(total_adjhor); + if (total_adjvert != NULL) + kfree(total_adjvert); + if (ssCompData.ix2_fm != NULL) + kfree(ssCompData.ix2_fm); + if (ssCompData.ix2_sn != NULL) + kfree(ssCompData.ix2_sn); + if (ssCompData.cx2_fm != NULL) + kfree(ssCompData.cx2_fm); + if (ssCompData.cx2_sn != NULL) + kfree(ssCompData.cx2_sn); + if (totCompData.ix_fm != NULL) + kfree(totCompData.ix_fm); + if (totCompData.ix_sn != NULL) + kfree(totCompData.ix_sn); + if (totCompData.cx_fm != NULL) + kfree(totCompData.cx_fm); + if (totCompData.cx_sn != NULL) + kfree(totCompData.cx_sn); + return ret; +} + +/** + * Perform a complete Data Test check of the IC + * @param path_limits name of Production Limit file to load or + * "NULL" if the limits data should be loaded by a .h file + * @param stop_on_fail if 1, the test flow stops at the first data check + * failure + * otherwise it keeps going performing all the selected test + * @param todo pointer to a TestToDo variable which select the test to do + * @return OK if success or an error code which specify the type of error + */ +int production_test_data(const char *path_limits, int stop_on_fail, + TestToDo *todo) +{ + int res = OK, ret; + + if (todo == NULL) { + pr_err("production_test_data: No TestToDo specified!! ERROR = %08X\n", + (ERROR_OP_NOT_ALLOW | ERROR_PROD_TEST_DATA)); + return ERROR_OP_NOT_ALLOW | ERROR_PROD_TEST_DATA; + } + + + pr_info("DATA Production test is starting...\n"); + + + ret = production_test_ms_raw(path_limits, stop_on_fail, todo); + res |= ret; + if (ret < 0) { + pr_err("production_test_data: production_test_ms_raw failed... ERROR = %08X\n", + ret); + if (stop_on_fail == 1) + goto END; + } + + + + ret = production_test_ms_cx(path_limits, stop_on_fail, todo); + res |= ret; + if (ret < 0) { + pr_err("production_test_data: production_test_ms_cx failed... ERROR = %08X\n", + ret); + if (stop_on_fail == 1) + goto END; + } + + + ret = production_test_ss_raw(path_limits, stop_on_fail, todo); + res |= ret; + if (ret < 0) { + pr_err("production_test_data: production_test_ss_raw failed... ERROR = %08X\n", + ret); + if (stop_on_fail == 1) + goto END; + } + + ret = production_test_ss_ix_cx(path_limits, stop_on_fail, todo); + res |= ret; + if (ret < 0) { + pr_err("production_test_data: production_test_ss_ix_cx failed... ERROR = %08X\n", + ret); + if (stop_on_fail == 1) + goto END; + } + +END: + freeLimitsFile(&limit_file); /* /< release the limit file loaded + * during the test */ + if (res < OK) + pr_err("DATA Production test failed!\n"); + else + pr_info("DATA Production test finished!\n"); + return res; +} + + +/*************** TP Sensitivity calibration API ********************/ + +/** + * Perform the Pre Calibration MS Test when the stimpad is down + * @param[out] frame pointer to the frame which will contain + * the average frame resulting from the test + * @param target reference value for the frame, each node should be + * around +-percentage% this value + * @param percentage percentage of the target value which define + * the valid interval for the frame, if <0 the test will be skipped + * @return OK if success or an error code which specify the type of error + */ +int tp_sensitivity_test_pre_cal_ms(MutualSenseFrame *finalFrame, short target, + int percentage) +{ + int ret = OK; + int count = 0, i = 0, j = 0; + short min, max; + MutualSenseFrame frame; + + finalFrame->node_data = NULL; + + + pr_info("%s: Start TP sensitivity MS Pre Cal...\n", __func__); + pr_info("%s: IMPORTANT!!! Stimpad should be on the display of the device!\n", + __func__); + ret = getMSFrame3(MS_STRENGTH, &frame); + if (ret < OK) { + pr_err("%s: can not read MS Frame... ERROR %08X\n", + __func__, ret); + goto ERROR; + } + + finalFrame->header = frame.header; + finalFrame->node_data_size = frame.node_data_size; + + finalFrame->node_data = (short *)kzalloc(frame.node_data_size * + sizeof(short), GFP_KERNEL); + if (finalFrame->node_data == NULL) { + pr_err("%s: can not allocate node_data ERROR %08X\n", + __func__, ERROR_ALLOC | ERROR_GET_FRAME); + ret = ERROR_ALLOC | ERROR_GET_FRAME; + goto ERROR; + } + + /* collecting frames */ + do { + for (i = 0; i < finalFrame->node_data_size; i++) { + finalFrame->node_data[i] += (frame.node_data[i] * 10) / + SENS_TEST_NUM_FRAMES; + } + + if (frame.node_data != NULL) { + kfree(frame.node_data); + frame.node_data = NULL; + } + + count++; + + /* exclude one more reading at the end*/ + if (count < SENS_TEST_NUM_FRAMES) + ret = getMSFrame3(MS_STRENGTH, &frame); + } while ((count < SENS_TEST_NUM_FRAMES) && (ret >= OK)); + + if (ret < OK) { + pr_err("%s: Error while capturing the frame %d! ERROR %08X\n", + __func__, count, ret); + goto ERROR; + } + + ret = OK; + /* check against +-percentage% target */ + pr_info("%s: Computing average frame...\n", __func__); + + min = target - (target * percentage / 100); + max = target + (target * percentage / 100); + + for (i = 0; i < finalFrame->header.force_node; i++) { + for (j = 0; j < finalFrame->header.sense_node; j++) { + finalFrame->node_data[i * + finalFrame->header.sense_node + + j] /= 10; + /*if percentage is <0 skip this test, just collect data */ + if ((percentage > 0) && + ((finalFrame->node_data[i * finalFrame->header. + sense_node + + j] > + max) || + (finalFrame->node_data[i * + finalFrame->header. + sense_node + + j] < + min))) { + pr_err("%s: MS Force Node[%d, %d] = %d exceed limit [%d, %d]\n", + __func__, i, j, + finalFrame->node_data[i * + finalFrame + ->header. + sense_node + j], + min, max); + ret = ERROR_TEST_CHECK_FAIL; + } + } + } + + + /* print average frame in the log */ + print_frame_short("MS FS Mean =", + array1dTo2d_short( + finalFrame->node_data, + finalFrame->node_data_size, + finalFrame->header.sense_node), + finalFrame->header.force_node, + finalFrame->header.sense_node); + + if (ret != OK) + pr_err("%s: TP sensitivity MS Pre Cal test FAILED... ERROR %08X\n", + __func__, ret); + else + pr_info("%s: TP sensitivity MS Pre Cal FINISHED!\n", + __func__); + + return ret; + + +ERROR: + if (frame.node_data != NULL) { + kfree(frame.node_data); + frame.node_data = NULL; + } + + + if (finalFrame->node_data != NULL) { + kfree(finalFrame->node_data); + finalFrame->node_data = NULL; + } + + return ret; +} + + + +/** + * Perform the Pre Calibration SS Test when the stimpad is down + * @param[out] frame pointer to the frame which will contain the average frame + * resulting from the test + * @param target reference value for the frame, each node should be around + * +-percentage% this value + * @param percentage percentage of the target value which define the valid + * interval for the frame + * @return OK if success or an error code which specify the type of error + */ +int tp_sensitivity_test_pre_cal_ss(SelfSenseFrame *finalFrame, short target, + int percentage) +{ + int ret = OK; + int count = 0, i = 0; + short min, max; + SelfSenseFrame frame; + int *temp_force = NULL; + int *temp_sense = NULL; + + finalFrame->force_data = NULL; + finalFrame->sense_data = NULL; + + pr_info("%s: Start TP sensitivity SS Pre Cal...\n", __func__); + pr_info("%s: IMPORTANT!!! Stimpad should be on the display of the device!\n", + __func__); + ret = getSSFrame3(SS_STRENGTH, &frame); + if (ret < OK) { + pr_err("%s: can not read SS Frame... ERROR %08X\n", + __func__, ret); + goto ERROR; + } + + finalFrame->header = frame.header; + + finalFrame->force_data = (short *)kzalloc(frame.header.force_node * + sizeof(short), GFP_KERNEL); + temp_force = (int *)kzalloc(frame.header.force_node * + sizeof(int), GFP_KERNEL); + finalFrame->sense_data = (short *)kzalloc(frame.header.sense_node * + sizeof(short), GFP_KERNEL); + temp_sense = (int *)kzalloc(frame.header.sense_node * + sizeof(int), GFP_KERNEL); + if (finalFrame->force_data == NULL || + temp_force == NULL || + finalFrame->sense_data == NULL || + temp_sense == NULL) { + + pr_err("%s: can not allocate memory ERROR %08X\n", + __func__, ERROR_ALLOC | ERROR_GET_FRAME); + ret = ERROR_ALLOC | ERROR_GET_FRAME; + goto ERROR; + } + + /* collecting frames */ + do { + for (i = 0; i < finalFrame->header.force_node; i++) + temp_force[i] += frame.force_data[i]; + + for (i = 0; i < finalFrame->header.sense_node; i++) + temp_sense[i] += frame.sense_data[i]; + + count++; + + if (frame.force_data != NULL) { + kfree(frame.force_data); + frame.force_data = NULL; + } + if (frame.sense_data != NULL) { + kfree(frame.sense_data); + frame.sense_data = NULL; + } + + /* exclude one more reading at the end*/ + if (count < SENS_TEST_NUM_FRAMES) + ret = getSSFrame3(SS_STRENGTH, &frame); + } while ((count < SENS_TEST_NUM_FRAMES) && (ret >= OK)); + + if (ret < OK) { + pr_err("%s: Error while capturing the frame %d! ERROR %08X\n", + __func__, count, ret); + goto ERROR; + } + + ret = OK; + + /* compute the average and check against +-percentage% target */ + min = target - (target * percentage / 100); + max = target + (target * percentage / 100); + + for (i = 0; i < finalFrame->header.force_node; i++) { + finalFrame->force_data[i] = temp_force[i] / + SENS_TEST_NUM_FRAMES; + if ((percentage > 0) && ((finalFrame->force_data[i] > max) || + (finalFrame->force_data[i] < min))) { + pr_err("%s: SS Force Node[%d] = %d exceed limit [%d, %d]\n", + __func__, i, finalFrame->force_data[i], + min, max); + ret = ERROR_TEST_CHECK_FAIL; + } + } + + for (i = 0; i < finalFrame->header.sense_node; i++) { + finalFrame->sense_data[i] = temp_sense[i] / + SENS_TEST_NUM_FRAMES; + if ((finalFrame->sense_data[i] > max) || + (finalFrame->sense_data[i] < min)) { + pr_err("%s: SS Sense Node[%d] = %d exceed limit [%d, %d]\n", + __func__, i, finalFrame->sense_data[i], + min, max); + ret = ERROR_TEST_CHECK_FAIL; + } + } + + /* print average frame in the log */ + print_frame_short("SS FS force Mean =", + array1dTo2d_short( + finalFrame->force_data, + finalFrame->header.force_node, + 1), + finalFrame->header.force_node, 1); + print_frame_short("SS FS sense Mean =", + array1dTo2d_short( + finalFrame->sense_data, + finalFrame->header.sense_node, + finalFrame->header.sense_node), + 1, finalFrame->header.sense_node); + + + kfree(temp_force); + temp_force = NULL; + + kfree(temp_sense); + temp_sense = NULL; + + if (ret < OK) + pr_err("%s: TP sensitivity SS Pre Cal test FAILED... ERROR %08X\n", + __func__, ret); + else { + pr_info("%s: TP sensitivity SS Pre Cal FINISHED!\n", + __func__); + ret = OK; + } + + return ret; + + +ERROR: + + kfree(temp_force); + temp_force = NULL; + + kfree(temp_sense); + temp_sense = NULL; + + kfree(frame.force_data); + frame.force_data = NULL; + + kfree(frame.sense_data); + frame.sense_data = NULL; + + kfree(finalFrame->force_data); + finalFrame->force_data = NULL; + + kfree(finalFrame->sense_data); + finalFrame->sense_data = NULL; + + return ret; +} + +/** + * Compute Digital gains for calibration + * @param frame pointer to the frame used as reference to compute the gains + * @param target reference target value for computing the gains + * @param saveGain if 1, will save the gain table into the chip otherwise will + * not save it + * @return OK if success or an error code which specify the type of error + */ +int tp_sensitivity_compute_gains(MutualSenseFrame *frame, short target, + int saveGain) +{ + int ret = OK; + int i = 0; + u8 *gains; + + if ((frame->node_data == NULL) || (frame->node_data_size == 0)) { + pr_err("%s: Invalid frame data passed as argument! ERROR %08X\n", + __func__, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + gains = kzalloc(frame->node_data_size * sizeof(u8), GFP_KERNEL); + if (gains == NULL) + return ERROR_ALLOC; + + pr_info("%s: Start to compute Digital Gains...\n", __func__); + for (i = 0; i < frame->node_data_size; i++) + gains[i] = ((target * 100) / frame->node_data[i]) > 255 ? + (u8)(255) : (u8)(((target * 100) / + frame->node_data[i])); + /* clamp the max value to 255 because gain is only one byte */ + + + /* print average frame in the log */ + print_frame_u8("MS Digital Gain =", + array1dTo2d_u8( + gains, + frame->node_data_size, + frame->header.sense_node), + frame->header.force_node, + frame->header.sense_node); + + + /* if(saveGain==1){ */ + /* write gains into the IC */ + ret = writeHostDataMemory(LOAD_SENS_CAL_COEFF, gains, + frame->header.force_node, + frame->header.sense_node, 0, 0, saveGain); + if (ret != OK) + pr_err("%s: impossible to write digital gains! ERROR %08X\n", + __func__, ret); + /* } */ + + if (ret < OK) + pr_err("%s: compute Digital Gains FAILED! ERROR %08X\n", + __func__, ret); + else { + pr_info("%s: compute Digital Gains FINISHED!\n", __func__); + ret = OK; + } + + kfree(gains); + return ret; +} + +/** + * Perform the Post Calibration MS Test when the stimpad is down + * @param[out] finalFrame pointer to the frame which will contain + * the average frame resulting from the test + * @param[out] deltas pointer to the frame which will contain + * the FS Uniform frame (worst_neighborhood/mean) + * @param target reference value for the frame, each node should be + * around +-percentage% this value + * @param percentage percentage of the target value which define + * the valid interval for the frame, if <0 the test will be skipped + * @param[out] mean_normal pointer to the variable which will contain the mean + * of the normal area + * @param[out] mean_edge pointer to the variable which will contain the mean of + * the edge area + * @return OK if success or an error code which specify the type of error + */ +int tp_sensitivity_test_post_cal_ms(MutualSenseFrame *finalFrame, + MutualSenseFrame *deltas, short target, + int percentage, int *mean_normal, + int *mean_edge) +{ + short currentNode; + int final_force_num; + int final_sense_num; + short *final_node; + int delta_sense_num; + short *delta_node; + short *delta; + short adjNode; + int ret = OK; + int i = 0, j = 0, min, max; + + + if ((finalFrame == NULL) || (deltas == NULL) || (mean_normal == NULL) || + (mean_edge == NULL)) { + pr_err("%s: Invalid arguments Passed! ERROR %08X\n", + __func__, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + *mean_normal = 0; + *mean_edge = 0; + + finalFrame->node_data = NULL; + deltas->node_data = NULL; + + pr_info("%s: Start TP sensitivity MS Post Cal...\n", __func__); + pr_info("%s: IMPORTANT!!! Stimpad should be on the display of the device!\n", + __func__); + + /* collect frames skipping the tests + print on the log */ + ret = tp_sensitivity_test_pre_cal_ms(finalFrame, target, -1); + if (ret < OK) { + pr_err("%s: can not collect MS Frame... ERROR %08X\n", + __func__, ret); + goto ERROR; + } + + + deltas->header = finalFrame->header; + deltas->node_data_size = finalFrame->node_data_size; + + deltas->node_data = (short *)kzalloc(deltas->node_data_size * + sizeof(short), GFP_KERNEL); + if (deltas->node_data == NULL) { + pr_err("%s: can not allocate deltas node_data ERROR %08X\n", + __func__, ERROR_ALLOC | ERROR_GET_FRAME); + ret = ERROR_ALLOC | ERROR_GET_FRAME; + goto ERROR; + } + + /* compute the average of the whole panel and check against + * +-percentage% target */ + pr_info("%s: Computing average of whole panel and delta for each node...\n", + __func__); + + final_force_num = finalFrame->header.force_node; + final_sense_num = finalFrame->header.sense_node; + final_node = finalFrame->node_data; + delta_sense_num = deltas->header.sense_node; + delta_node = deltas->node_data; + + + for (i = 0; i < final_force_num; i++) { + for (j = 0; j < final_sense_num; j++) { + currentNode = finalFrame->node_data[i * + finalFrame->header. + sense_node + j]; + delta = &delta_node[i * delta_sense_num + j]; + + if ((i == 0) || + (i == (final_force_num - 1)) || + (j == 0) || + (j == (final_sense_num - 1))) { + /* edge nodes */ + *mean_edge += currentNode; + if ((i == 0) || + (i == final_force_num - 1)) { + /* need to check adj node up or down for + * nodes in the corners */ + if ((i == 0) && + ((j == 0) || + (j == final_sense_num - 1))) { + adjNode = currentNode - + final_node[(i + 1) * + final_sense_num + j]; + if (abs(adjNode) > *delta) + *delta = abs(adjNode); + } + + if ((i == (final_force_num - 1)) && + ((j == 0) || + (j == final_sense_num - 1))) { + adjNode = currentNode - + final_node[(i - 1) * + final_sense_num + j]; + if (abs(adjNode) > *delta) + *delta = abs(adjNode); + } + + /* scan the row */ + if ((j - 1) >= 0) { + adjNode = currentNode - + final_node[i * + final_sense_num + + (j - 1)]; + if (abs(adjNode) > *delta) + *delta = abs(adjNode); + } + + if ((j + 1) < final_sense_num) { + adjNode = currentNode - + final_node[i * + final_sense_num + + (j + 1)]; + if (abs(adjNode) > *delta) + *delta = abs(adjNode); + } + } + + if ((j == 0) || + (j == final_sense_num - 1)) { + /* scan the column */ + if ((i - 1) >= 0) { + adjNode = currentNode - + final_node[(i - 1) * + final_sense_num + j]; + if (abs(adjNode) > *delta) + *delta = abs(adjNode); + } + + if ((i + 1) < final_force_num) { + adjNode = currentNode - + final_node[(i + 1) * + final_sense_num + j]; + if (abs(adjNode) > *delta) + *delta = abs(adjNode); + } + } + } else { + /*normal nodes */ + *mean_normal += currentNode; + + /* picking up the worst difference between + * one pixel and its neighbors */ + if ((i - 1) >= 1) { + adjNode = currentNode - + final_node[(i - 1) * + final_sense_num + j]; + if (abs(adjNode) > *delta) + *delta = abs(adjNode); + } + + if ((i + 1) < (final_force_num - 1)) { + adjNode = currentNode - + final_node[(i + 1) * + final_sense_num + j]; + if (abs(adjNode) > *delta) + *delta = abs(adjNode); + } + if ((j - 1) >= 1) { + adjNode = currentNode - + final_node[i * + final_sense_num + (j - 1)]; + if (abs(adjNode) > *delta) + *delta = abs(adjNode); + } + + if ((j + 1) < (final_sense_num - 1)) { + adjNode = currentNode - + final_node[i * + final_sense_num + (j + 1)]; + if (abs(adjNode) > *delta) + *delta = abs(adjNode); + } + } + } + } + + *mean_normal /= (finalFrame->header.force_node - 2) * + (finalFrame->header.sense_node - 2); + *mean_edge /= (finalFrame->header.force_node * 2) + + (finalFrame->header.sense_node - 2) * 2; + + pr_info("%s: Normal Frame average = %d\n", __func__, *mean_normal); + pr_info("%s: Edge Frame average = %d\n", __func__, *mean_edge); + /* compute the average and check against +-% target */ + min = target - (target * percentage / 100); + max = target + (target * percentage / 100); + + if ((percentage > 0) && ((*mean_normal < min) || (*mean_normal > + max))) { + pr_err("%s: Normal Frame average = %d exceed limit [%d, %d]\n", + __func__, *mean_normal, min, max); + ret = ERROR_TEST_CHECK_FAIL; + } + + if ((percentage > 0) && ((*mean_edge < min) || (*mean_edge > max))) { + pr_err("%s: Edge Frame average = %d exceed limit [%d, %d]\n", + __func__, *mean_edge, min, max); + ret = ERROR_TEST_CHECK_FAIL; + } + + for (i = 0; i < deltas->header.force_node; i++) { + for (j = 0; j < deltas->header.sense_node; j++) { + if ((i == 0) || (i == deltas->header.force_node) || + (j == 0) || (j == deltas->header.sense_node)) + deltas->node_data[i * + deltas->header.sense_node + + j] = + deltas->node_data[i * + deltas->header. + sense_node + j] * + 100 / + (*mean_edge); + else + deltas->node_data[i * + deltas->header.sense_node + + j] = + deltas->node_data[i * + deltas->header. + sense_node + j] * + 100 / + (*mean_normal); + + if ((percentage > 0) && (deltas->node_data[i * + deltas-> + header. + sense_node + + j] > + percentage)) { + pr_err("%s: Delta Node[%d, %d] = %d exceed limit [%d]\n", + __func__, i, j, + deltas->node_data[i * + deltas + ->header.sense_node + + j], percentage); + ret = ERROR_TEST_CHECK_FAIL; + } + } + } + + + /* print average frame in the log */ + print_frame_short("FS Uniform (%) =", + array1dTo2d_short( + deltas->node_data, + deltas->node_data_size, + deltas->header.sense_node), + deltas->header.force_node, + deltas->header.sense_node); + + + if (ret < OK) + pr_err("%s: TP sensitivity MS Post Cal test FAILED... ERROR %08X\n", + __func__, ret); + else { + pr_info("%s: TP sensitivity MS Post Cal FINISHED!\n", + __func__); + ret = OK; + } + + return ret; + + +ERROR: + if (deltas->node_data != NULL) { + kfree(deltas->node_data); + deltas->node_data = NULL; + } + + + if (finalFrame->node_data != NULL) { + kfree(finalFrame->node_data); + finalFrame->node_data = NULL; + } + + return ret; +} + + +/** + * Compute Digital gains for calibration + * @param enter if =1 turn on TP Sensitivity mode, otherwise will turn it off + * @param saveGain if 1, will save the gain table into the chip otherwise will + * not save it + * @return OK if success or an error code which specify the type of error + */ +int tp_sensitivity_mode(u8 enter, int saveGain) +{ + int res, ret = OK; + u8 cmd[4] = { 0xC0, 0x00, 0x00, 0x00 }; + u8 sett = SPECIAL_WRITE_HOST_MEM_TO_FLASH; + u8 parameter[2] = { 0x00, 0x01 }; + + pr_info("%s: Start TP Sensitivity Mode... enter = %02X\n", + __func__, enter); + if (enter == 1) { + /* enter TP Sensitivity mode*/ + ret = fts_enableInterrupt(false); + pr_info("%s: Entering TP Sensitivity Mode disabling algos...\n", + __func__); + cmd[3] = 0x01; + res = fts_writeFwCmd(cmd, 4); + if (res < OK) + pr_err("%s: Error while turning on TP Sens Mode! ERROR %08X\n", + __func__, res); + else { + ret = writeSysCmd(SYS_CMD_CX_TUNING, parameter, 2); + if (ret < OK) + pr_err("%s: error while performing Single Ended Special Autotune! ERROR %08X\n", + __func__, ret); + } + } else { + /* exit TP Sensitivity mode*/ + pr_info("%s: Exiting TP Sensitivity Mode enabling algos...\n", + __func__); + res = fts_writeFwCmd(cmd, 4); + if (res < OK) + pr_err("%s: Error while turning off TP Sens Mode! ERROR %08X\n", + __func__, res); + + if (saveGain == 1) { + pr_info("%s: Trigger writing gains into the flash...\n", + __func__); + ret = writeSysCmd(SYS_CMD_SPECIAL, &sett, 1); + if (ret < OK) + pr_err("%s: error while writing gains into the flash! ERROR %08X\n", + __func__, res); + } + + res |= senseOn(); + res |= fts_enableInterrupt(true); + } + + res |= ret; + + if (res < OK) + pr_err("%s: TP Sensitivity Mode... ERROR %08X!\n", + __func__, res); + else + pr_info("%s: TP Sensitivity Mode FINISHED!\n", __func__); + + return res; +} + + +/** + * Compute Digital gains for calibration + * @param scan select the scan mode which should be enabled + * @param enableGains =1 apply gains when computing the strength otherwise + * the gains will be ignored + * @return OK if success or an error code which specify the type of error + */ +int tp_sensitivity_set_scan_mode(u8 scan, int enableGains) +{ + int res, ret = OK; + u8 cmd[4] = { 0xC0, 0x00, 0x01, 0x00 }; + + + pr_info("%s: Set TP Sensitivity Scan Mode... scan = %02X, enableGains = %d\n", + __func__, scan, enableGains); + + + if (enableGains == 1) { + /* Consider Sensitivity Gains when computing Strength */ + cmd[3] = 0x01; + ret = fts_writeFwCmd(cmd, 4); + if (ret < OK) + pr_err("%s: Error while enabling Gains in TP Sens Mode! ERROR %08X\n", + __func__, ret); + } else { + /* Exclude Sensitivity Gains when computing Strength */ + ret = fts_writeFwCmd(cmd, 4); + if (ret < OK) + pr_err("%s: Error while disabling Gain in TP Sens Mode! ERROR %08X\n", + __func__, ret); + } + + res = setScanMode(SCAN_MODE_LOCKED, scan); + if (res < OK) + pr_err("Error while setting the scan frequency... ERROR %08X\n", + res); + + res |= ret; + + if (res < OK) + pr_err("%s: Set TP Sensitivity Scan Mode... ERROR %08X!\n", + __func__, res); + else + pr_info("%s: Set TP Sensitivity Scan FINISHED!\n", __func__); + + return res; +} + + + + +/** + * Compute the standard deviation for each node form a series of frames + * @param numFrames number of frames to collect to compute the standard + * deviation + * @param[out] std pointer to the frame which will contain the standard + * deviation for each node + * @return OK if success or an error code which specify the type of error + */ +int tp_sensitivity_test_std_ms(int numFrames, MutualSenseFrame *std) +{ + int ret = OK; + int i = 0, count = 0; + MutualSenseFrame frame; + int *mean = NULL;/* store the mean value for each node */ + unsigned long *stdTemp = NULL; + + + if (std == NULL) { + pr_err("%s: Invalid arguments Passed! ERROR %08X\n", + __func__, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + std->node_data = NULL; + + pr_info("%s: Start TP sensitivity STD... collecting %d frames!\n", + __func__, numFrames); + + /* collect frames skipping the tests + print on the log */ + ret = getMSFrame3(MS_STRENGTH, &frame); + if (ret < OK) { + pr_err("%s: can not read MS Frame... ERROR %08X\n", + __func__, ret); + goto ERROR; + } + + std->header = frame.header; + std->node_data_size = frame.node_data_size; + + std->node_data = (short *)kzalloc(std->node_data_size * sizeof(short), + GFP_KERNEL); + mean = (int *)kzalloc(std->node_data_size * sizeof(int), GFP_KERNEL); + stdTemp = (unsigned long *)kzalloc(std->node_data_size * + sizeof(unsigned long), + GFP_KERNEL); + if (std->node_data == NULL || + mean == NULL || + stdTemp == NULL) { + pr_err("%s: can not allocate memory ERROR %08X\n", + __func__, ERROR_ALLOC | ERROR_GET_FRAME); + ret = ERROR_ALLOC | ERROR_GET_FRAME; + goto ERROR; + } + + /* collecting frames */ + do { + for (i = 0; i < frame.node_data_size; i++) { + mean[i] += frame.node_data[i]; + stdTemp[i] += frame.node_data[i] * frame.node_data[i]; + } + count++; + + if (frame.node_data != NULL) { + kfree(frame.node_data); + frame.node_data = NULL; + } + + /* exclude one more reading at the end*/ + if (count < numFrames) + ret = getMSFrame3(MS_STRENGTH, &frame); + } while ((count < numFrames) && (ret >= OK)); + + if (ret < OK) { + pr_err("%s: error while collecting the frames! ERROR%08X\n", + __func__, ret); + goto ERROR; + } + + /* compute the average for each node */ + pr_info("%s: Computing std for each node...\n", __func__); + + for (i = 0; i < std->node_data_size; i++) { + mean[i] /= numFrames; + stdTemp[i] = stdTemp[i] / numFrames - (mean[i] * mean[i]); + std->node_data[i] = (short)int_sqrt(stdTemp[i]); + } + + kfree(stdTemp); + stdTemp = NULL; + kfree(mean); + mean = NULL; + + /* print average frame in the log */ + print_frame_short("STD =", + array1dTo2d_short( + std->node_data, + std->node_data_size, + std->header.sense_node), + std->header.force_node, + std->header.sense_node); + + if (ret < OK) + pr_err("%s: TP sensitivity STD test FAILED... ERROR %08X\n", + __func__, ret); + else { + pr_info("%s: TP sensitivity STD FINISHED!\n", + __func__); + ret = OK; + } + + return ret; + +ERROR: + + kfree(frame.node_data); + frame.node_data = NULL; + + kfree(std->node_data); + std->node_data = NULL; + + kfree(stdTemp); + stdTemp = NULL; + + kfree(mean); + mean = NULL; + + return ret; +} + + +/** + * Retrieve the actual Test Limit data from the system (bin file or header + * file) + * @param path name of Production Test Limit file to load or + * "NULL" if the limits data should be loaded by a .h file + * @param file pointer to the LimitFile struct which will contains the limits + * data + * @return OK if success or an error code which specify the type of error + */ +int getLimitsFile(const char *path, LimitFile *file) +{ + const struct firmware *fw = NULL; + struct device *dev = NULL; + int fd = -1; + + pr_info("Get Limits File starting... %s\n", path); + + if (file->data != NULL) { + /* to avoid memory leak on consecutive call of + * the function with the same pointer */ + pr_err("Pointer to Limits Data already contains something... freeing its content!\n"); + kfree(file->data); + file->data = NULL; + file->size = 0; + } + + strlcpy(file->name, path, MAX_LIMIT_FILE_NAME); + if (strncmp(path, "NULL", 4) == 0) { +#ifdef LIMITS_H_FILE + pr_info("Loading Limits File from .h!\n"); + file->size = LIMITS_SIZE_NAME; + file->data = (char *)kmalloc((file->size) * sizeof(char), + GFP_KERNEL); + if (file->data != NULL) { + memcpy(file->data, (char *)(LIMITS_ARRAY_NAME), + file->size); + return OK; + } else { + pr_err("Error while allocating data... ERROR %08X\n", + path, ERROR_ALLOC); + return ERROR_ALLOC; + } +#else + pr_err("limit file path NULL... ERROR %08X\n", + ERROR_FILE_NOT_FOUND); + return ERROR_FILE_NOT_FOUND; +#endif + } else { + dev = getDev(); + if (dev != NULL) { + pr_info("Loading Limits File from .csv!\n"); + fd = request_firmware(&fw, path, dev); + if (fd == 0) { + pr_info("Start to copy %s...\n", path); + file->size = fw->size; + file->data = (char *)kmalloc((file->size) * + sizeof(char), + GFP_KERNEL); + if (file->data != NULL) { + memcpy(file->data, (char *)fw->data, + file->size); + pr_info("Limit file Size = %d\n", + file->size); + release_firmware(fw); + return OK; + } else { + pr_err("Error while allocating data... ERROR %08X\n", + ERROR_ALLOC); + release_firmware(fw); + return ERROR_ALLOC; + } + } else { + pr_err("Request the file %s failed... ERROR %08X\n", + path, ERROR_FILE_NOT_FOUND); + return ERROR_FILE_NOT_FOUND; + } + } else { + pr_err("Error while getting the device ERROR %08X\n", + ERROR_FILE_READ); + return ERROR_FILE_READ; + } + } +} + +/** + * Reset and release the memory which store a Production Limit File previously + * loaded + * @param file pointer to the LimitFile struct to free + * @return OK if success or an error code which specify the type of error + */ + +int freeLimitsFile(LimitFile *file) +{ + pr_info("Freeing Limit File ...\n"); + if (file != NULL) { + if (file->data != NULL) { + kfree(file->data); + file->data = NULL; + } else + pr_err("Limit File was already freed!\n"); + file->size = 0; + strlcpy(file->name, " ", MAX_LIMIT_FILE_NAME); + return OK; + } else { + pr_err("Passed a NULL argument! ERROR %08X\n", + ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } +} + +/** + * Reset and release the memory which store the current Limit File + * previously loaded + * @return OK if success or an error code which specify the type of error + */ +int freeCurrentLimitsFile(void) +{ + return freeLimitsFile(&limit_file); +} + +/** + * Parse the raw data read from a Production test limit file in order + * to find the specified information + * If no limits file data are passed, the function loads and stores the limit + * file from the system + * @param path name of Production Test Limit file to load or + * "NULL" if the limits data should be loaded by a .h file + * @param file pointer to LimitFile struct that should be parsed or + * NULL if the limit file in the system should be loaded and then parsed + * @param label string which identify a particular set of data in the file that + * want to be loaded + * @param data pointer to the pointer which will contains the specified limits + * data + * as 1 dimension matrix with data arranged row after row + * @param row pointer to a int variable which will contain the number of row of + * data + * @param column pointer to a int variable which will contain the number of + * column of data + * @return OK if success or an error code which specify the type of error + */ +int parseProductionTestLimits(const char *path, LimitFile *file, char *label, + int **data, int *row, int *column) +{ + int find = 0; + char *token = NULL; + int i = 0; + int j = 0; + int z = 0; + + char *line2 = NULL; + char line[800]; + char *buf = NULL; + int n, size, pointer = 0, ret = OK; + char *data_file = NULL; + + if (file == NULL || strcmp(path, file->name) != 0 || file->size == 0) { + struct fts_ts_info *info = dev_get_drvdata(getDev()); + const char *limits_file = info->board->limits_name; + + pr_info("No limit File data passed... try to get them from the system!\n"); + ret = getLimitsFile(limits_file, &limit_file); + if (ret < OK) { + pr_err("parseProductionTestLimits: ERROR %08X\n", + ERROR_FILE_NOT_FOUND); + return ERROR_FILE_NOT_FOUND; + } + size = limit_file.size; + data_file = limit_file.data; + } else { + pr_info("Limit File data passed as arguments!\n"); + size = file->size; + data_file = file->data; + } + + + + pr_info("The size of the limits file is %d bytes...\n", size); + + + + while (find == 0) { + /* start to look for the wanted label */ + if (readLine(&data_file[pointer], line, size - pointer, &n) < + 0) { + find = -1; + break; + } + pointer += n; + if (line[0] == '*') { + /* each header row start with * ex. *label,n_row,n_colum */ + line2 = kstrdup(line, GFP_KERNEL); + if (line2 == NULL) { + pr_err("parseProductionTestLimits: kstrdup ERROR %08X\n", + ERROR_ALLOC); + ret = ERROR_ALLOC; + goto END; + } + buf = line2; + line2 += 1; + token = strsep(&line2, ","); + if (strcmp(token, label) == 0) { + /* if the row is the wanted one, r + * retrieve rows and columns info */ + find = 1; + token = strsep(&line2, ","); + if (token != NULL) { + sscanf(token, "%d", row); + pr_info("Row = %d\n", *row); + } else { + pr_err("parseProductionTestLimits 1: ERROR %08X\n", + ERROR_FILE_PARSE); + ret = ERROR_FILE_PARSE; + goto END; + } + token = strsep(&line2, ","); + if (token != NULL) { + sscanf(token, "%d", column); + pr_info("Column = %d\n", *column); + } else { + pr_err("parseProductionTestLimits 2: ERROR %08X\n", + ERROR_FILE_PARSE); + ret = ERROR_FILE_PARSE; + goto END; + } + + kfree(buf); + buf = NULL; + *data = (int *)kmalloc(((*row) * (*column)) * + sizeof(int), GFP_KERNEL); + /* allocate the memory for containing the data */ + j = 0; + if (*data == NULL) { + pr_err("parseProductionTestLimits: ERROR %08X\n", + ERROR_ALLOC); + ret = ERROR_ALLOC; + goto END; + } + + + /* start to read the data */ + for (i = 0; i < *row; i++) { + if (readLine(&data_file[pointer], line, + size - pointer, &n) < 0) { + pr_err("parseProductionTestLimits : ERROR %08X\n", + ERROR_FILE_READ); + ret = ERROR_FILE_READ; + goto END; + } + pointer += n; + line2 = kstrdup(line, GFP_KERNEL); + if (line2 == NULL) { + pr_err("parseProductionTestLimits: kstrdup ERROR %08X\n", + ERROR_ALLOC); + ret = ERROR_ALLOC; + goto END; + } + buf = line2; + token = strsep(&line2, ","); + for (z = 0; (z < *column) && (token != + NULL); + z++) { + sscanf(token, "%d", ((*data) + + j)); + j++; + token = strsep(&line2, ","); + } + kfree(buf); + buf = NULL; + } + if (j == ((*row) * (*column))) { + /* check that all the data are read */ + pr_info("READ DONE!\n"); + ret = OK; + goto END; + } + pr_err("parseProductionTestLimits 3: ERROR %08X\n", + ERROR_FILE_PARSE); + ret = ERROR_FILE_PARSE; + goto END; + } + kfree(buf); + buf = NULL; + } + } + pr_err("parseProductionTestLimits: ERROR %08X\n", + ERROR_LABEL_NOT_FOUND); + ret = ERROR_LABEL_NOT_FOUND; +END: + if (buf != NULL) + kfree(buf); + return ret; +} + + +/** + * Read one line of a text file passed as array of byte and terminate it with + * a termination character '\0' + * @param data text file as array of bytes + * @param line pointer to an array of char that will contain the line read + * @param size size of data + * @param n pointer to a int variable which will contain the number of + * characters of the line + * @return OK if success or an error code which specify the type of error + */ +int readLine(char *data, char *line, int size, int *n) +{ + int i = 0; + + if (size < 1) + return ERROR_OP_NOT_ALLOW; + + while (data[i] != '\n' && i < size) { + line[i] = data[i]; + i++; + } + *n = i + 1; + line[i] = '\0'; + + return OK; +} diff --git a/fts_lib/ftsTest.h b/fts_lib/ftsTest.h new file mode 100644 index 0000000..bb899ad --- /dev/null +++ b/fts_lib/ftsTest.h @@ -0,0 +1,500 @@ +/* + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS API for MP test ** + * * + ************************************************************************** + ************************************************************************** + * + */ + +/*! + * \file ftsTest.h + * \brief Contains all the definitions and structs related to the Mass + *Production Test + */ + +#ifndef FTS_TEST_H +#define FTS_TEST_H + +#include "ftsSoftware.h" +#include "ftsFrame.h" + +#ifndef LIMITS_H_FILE +/* /< Name of the Production Test Limit File */ +#define LIMITS_FILE "stm_fts_production_limits.csv" +#else +#define LIMITS_FILE "NULL" +#endif + +#define WAIT_FOR_FRESH_FRAMES 200 /* /< Time in ms to wait after + * start to sensing before + * reading a frame */ +#define WAIT_AFTER_SENSEOFF 50 /* /< Time in ms to wait after + * stop sensing and before + * reading a frame from + * memory */ + +#define NO_INIT 0 /* /< No Initialization required + * during the MP */ + +#define RETRY_INIT_BOOT 3 /* /< number of retry of the + * init process at boot */ + +#define SENS_TEST_NUM_FRAMES 100 /* /< number of frames to read */ +#define SENS_TEST_PERC_TARGET_PRECAL 20 /* /< +-% of target value within + * all the node of the frames + * should be contained */ +#define SENS_TEST_PERC_TARGET_POSTCAL 4 /* /< +-% of target value within + * should be contained the + * avarege of the nodes and the + * deltas for each node */ + +/** @defgroup mp_test Mass Production Test + * Mass production test API. + * Mass Production Test (MP) should be executed at least one time in the life + * of every device \n + * It used to verify that tit is not present any hardware damage and + * initialize some value of the chip in order to guarantee the working + * performance \n + * The MP test is made up by 3 steps: + * - ITO test = production_test_ito() \n + * - Initialization = production_test_initialization() \n + * - Data Test = production_test_data(), + * it is possible to select which items test thanks to the TestToDo struct\n + * To execute the Data Test it is mandatory load some thresholds that + * are stored in the Limit File. + * @{ + */ + +/** @defgroup limit_file Limit File + * @ingroup mp_test + * Production Test Limit File is a csv which contains thresholds of the data to + * test. + * This file can be loaded from the file system or stored as a header file + * according to the LIMITS_H_FILE define \n + * For each selectable test item there can be one or more associated labels + * which store the corresponding thresholds \n + * @{ + */ +/* LABELS PRODUCTION TEST LIMITS FILE */ +/** @defgroup test_labels Test Items Labels + * @ingroup limit_file + * Labels present in the Limit File and associated to the test items of + * TestToDo + * @{ + */ +#define MS_RAW_MIN_MAX "MS_RAW_DATA_MIN_MAX" +#define MS_RAW_EACH_NODE_MIN "MS_RAW_DATA_EACH_MIN" +#define MS_RAW_EACH_NODE_MAX "MS_RAW_DATA_EACH_MAX" +#define MS_RAW_GAP "MS_RAW_DATA_GAP" +#define MS_RAW_ADJH "MS_RAW_DATA_ADJ_HORIZONTAL" +#define MS_RAW_ADJV "MS_RAW_DATA_ADJ_VERTICAL" +#define MS_RAW_ITO_ADJH "MS_RAW_ITO_DATA_ADJ_HORIZONTAL" +#define MS_RAW_ITO_ADJV "MS_RAW_ITO_DATA_ADJ_VERTICAL" +#define MS_RAW_ITO_MIN_MAX "MS_RAW_ITO_MIN_MAX" +#define MS_RAW_ITO_EACH_NODE_MIN "MS_RAW_ITO_DATA_MIN" +#define MS_RAW_ITO_EACH_NODE_MAX "MS_RAW_ITO_DATA_MAX" +#define MS_RAW_LP_MIN_MAX "MS_RAW_LOWPOWER_DATA_MIN_MAX" +#define MS_RAW_LP_EACH_NODE_MIN "MS_RAW_LOWPOWER_DATA_EACH_MIN" +#define MS_RAW_LP_EACH_NODE_MAX "MS_RAW_LOWPOWER_DATA_EACH_MAX" +#define MS_RAW_LP_GAP "MS_RAW_LOWPOWER_DATA_GAP" +#define MS_RAW_LP_ADJH "MS_RAW_LOWPOWER_DATA_ADJ_HORIZONTAL" +#define MS_RAW_LP_ADJV "MS_RAW_LOWPOWER_DATA_ADJ_VERTICAL" +#define MS_RAW_ADJH_GAP "MS_RAW_DATA_ADJ_HORIZONTAL_P2P" +#define MS_RAW_ADJV_GAP "MS_RAW_DATA_ADJ_VERTICAL_P2P" +#define MS_RAW_ADJ_PEAK "MS_RAW_DATA_ADJ_PEAK" +#define MS_CX1_MIN_MAX "MS_TOUCH_ACTIVE_CX1_MIN_MAX" +#define MS_CX2_MAP_MIN "MS_TOUCH_ACTIVE_CX2_MIN" +#define MS_CX2_MAP_MAX "MS_TOUCH_ACTIVE_CX2_MAX" +#define MS_CX2_ADJH_MAP_MAX "MS_TOUCH_ACTIVE_CX2_ADJ_HORIZONTAL" +#define MS_CX2_ADJV_MAP_MAX "MS_TOUCH_ACTIVE_CX2_ADJ_VERTICAL" +#define MS_TOTAL_CX_MAP_MIN "MS_TOUCH_ACTIVE_TOTAL_CX_MIN" +#define MS_TOTAL_CX_MAP_MAX "MS_TOUCH_ACTIVE_TOTAL_CX_MAX" +#define MS_TOTAL_CX_ADJH_MAP_MAX "MS_TOUCH_ACTIVE_TOTAL_CX_ADJ_HORIZONTAL" +#define MS_TOTAL_CX_ADJV_MAP_MAX "MS_TOUCH_ACTIVE_TOTAL_CX_ADJ_VERTICAL" +#define MS_CX1_LP_MIN_MAX "MS_TOUCH_LOWPOWER_CX1_MIN_MAX" +#define MS_CX2_LP_MAP_MIN "MS_TOUCH_LOWPOWER_CX2_MIN" +#define MS_CX2_LP_MAP_MAX "MS_TOUCH_LOWPOWER_CX2_MAX" +#define MS_CX2_ADJH_LP_MAP_MAX "MS_TOUCH_LOWPOWER_CX2_ADJ_HORIZONTAL" +#define MS_CX2_ADJV_LP_MAP_MAX "MS_TOUCH_LOWPOWER_CX2_ADJ_VERTICAL" +#define MS_TOTAL_CX_LP_MAP_MIN "MS_TOUCH_LOWPOWER_TOTAL_CX_MIN" +#define MS_TOTAL_CX_LP_MAP_MAX "MS_TOUCH_LOWPOWER_TOTAL_CX_MAX" +#define MS_TOTAL_CX_ADJH_LP_MAP_MAX "MS_TOUCH_LOWPOWER_TOTAL_CX_ADJ_HORIZONTAL" +#define MS_TOTAL_CX_ADJV_LP_MAP_MAX "MS_TOUCH_LOWPOWER_TOTAL_CX_ADJ_VERTICAL" +#define SS_RAW_FORCE_MIN_MAX "SS_RAW_DATA_FORCE_MIN_MAX" +#define SS_RAW_FORCE_EACH_NODE_MIN "SS_RAW_DATA_FORCE_EACH_MIN" +#define SS_RAW_FORCE_EACH_NODE_MAX "SS_RAW_DATA_FORCE_EACH_MAX" +#define SS_RAW_SENSE_MIN_MAX "SS_RAW_DATA_SENSE_MIN_MAX" +#define SS_RAW_SENSE_EACH_NODE_MIN "SS_RAW_DATA_SENSE_EACH_MIN" +#define SS_RAW_SENSE_EACH_NODE_MAX "SS_RAW_DATA_SENSE_EACH_MAX" +#define SS_RAW_FORCE_GAP "SS_RAW_DATA_FORCE_GAP" +#define SS_RAW_SENSE_GAP "SS_RAW_DATA_SENSE_GAP" +#define SS_RAW_LP_FORCE_MIN_MAX "SS_RAW_LOWPOWER_DATA_FORCE_MIN_MAX" +#define SS_RAW_LP_SENSE_MIN_MAX "SS_RAW_LOWPOWER_DATA_SENSE_MIN_MAX" +#define SS_RAW_LP_FORCE_EACH_NODE_MIN "SS_RAW_LOWPOWER_DATA_FORCE_EACH_MIN" +#define SS_RAW_LP_FORCE_EACH_NODE_MAX "SS_RAW_LOWPOWER_DATA_FORCE_EACH_MAX" +#define SS_RAW_LP_SENSE_MIN_MAX "SS_RAW_LOWPOWER_DATA_SENSE_MIN_MAX" +#define SS_RAW_LP_SENSE_EACH_NODE_MIN "SS_RAW_LOWPOWER_DATA_SENSE_EACH_MIN" +#define SS_RAW_LP_SENSE_EACH_NODE_MAX "SS_RAW_LOWPOWER_DATA_SENSE_EACH_MAX" +#define SS_RAW_LP_FORCE_GAP "SS_RAW_LOWPOWER_DATA_FORCE_GAP" +#define SS_RAW_LP_SENSE_GAP "SS_RAW_LOWPOWER_DATA_SENSE_GAP" +#define SS_IX1_FORCE_MIN_MAX "SS_TOUCH_ACTIVE_IX1_FORCE_MIN_MAX" +#define SS_IX1_SENSE_MIN_MAX "SS_TOUCH_ACTIVE_IX1_SENSE_MIN_MAX" +#define SS_CX1_FORCE_MIN_MAX "SS_TOUCH_ACTIVE_CX1_FORCE_MIN_MAX" +#define SS_CX1_SENSE_MIN_MAX "SS_TOUCH_ACTIVE_CX1_SENSE_MIN_MAX" +#define SS_IX2_FORCE_MAP_MIN "SS_TOUCH_ACTIVE_IX2_FORCE_MIN" +#define SS_IX2_FORCE_MAP_MAX "SS_TOUCH_ACTIVE_IX2_FORCE_MAX" +#define SS_IX2_SENSE_MAP_MIN "SS_TOUCH_ACTIVE_IX2_SENSE_MIN" +#define SS_IX2_SENSE_MAP_MAX "SS_TOUCH_ACTIVE_IX2_SENSE_MAX" +#define SS_IX2_FORCE_ADJV_MAP_MAX "SS_TOUCH_ACTIVE_IX2_ADJ_VERTICAL" +#define SS_IX2_SENSE_ADJH_MAP_MAX "SS_TOUCH_ACTIVE_IX2_ADJ_HORIZONTAL" +#define SS_CX2_FORCE_MAP_MIN "SS_TOUCH_ACTIVE_CX2_FORCE_MIN" +#define SS_CX2_FORCE_MAP_MAX "SS_TOUCH_ACTIVE_CX2_FORCE_MAX" +#define SS_CX2_SENSE_MAP_MIN "SS_TOUCH_ACTIVE_CX2_SENSE_MIN" +#define SS_CX2_SENSE_MAP_MAX "SS_TOUCH_ACTIVE_CX2_SENSE_MAX" +#define SS_CX2_FORCE_ADJV_MAP_MAX "SS_TOUCH_ACTIVE_CX2_ADJ_VERTICAL" +#define SS_CX2_SENSE_ADJH_MAP_MAX "SS_TOUCH_ACTIVE_CX2_ADJ_HORIZONTAL" + +/* TOTAL SS */ +#define SS_TOTAL_IX_FORCE_MAP_MIN "SS_TOUCH_ACTIVE_TOTAL_IX_FORCE_MIN" +#define SS_TOTAL_IX_FORCE_MAP_MAX "SS_TOUCH_ACTIVE_TOTAL_IX_FORCE_MAX" +#define SS_TOTAL_IX_SENSE_MAP_MIN "SS_TOUCH_ACTIVE_TOTAL_IX_SENSE_MIN" +#define SS_TOTAL_IX_SENSE_MAP_MAX "SS_TOUCH_ACTIVE_TOTAL_IX_SENSE_MAX" +#define SS_TOTAL_IX_FORCE_ADJV_MAP_MAX "SS_TOUCH_ACTIVE_TOTAL_IX_ADJ_VERTICAL" +#define SS_TOTAL_IX_SENSE_ADJH_MAP_MAX \ + "SS_TOUCH_ACTIVE_TOTAL_IX_ADJ_HORIZONTAL" +#define SS_TOTAL_CX_FORCE_MAP_MIN "SS_TOUCH_ACTIVE_TOTAL_CX_FORCE_MIN" +#define SS_TOTAL_CX_FORCE_MAP_MAX "SS_TOUCH_ACTIVE_TOTAL_CX_FORCE_MAX" +#define SS_TOTAL_CX_SENSE_MAP_MIN "SS_TOUCH_ACTIVE_TOTAL_CX_SENSE_MIN" +#define SS_TOTAL_CX_SENSE_MAP_MAX "SS_TOUCH_ACTIVE_TOTAL_CX_SENSE_MAX" +#define SS_TOTAL_CX_FORCE_ADJV_MAP_MAX "SS_TOUCH_ACTIVE_TOTAL_CX_ADJ_VERTICAL" +#define SS_TOTAL_CX_SENSE_ADJH_MAP_MAX \ + "SS_TOUCH_ACTIVE_TOTAL_CX_ADJ_HORIZONTAL" + +/* Idle (LP) version*/ +#define SS_IX1_LP_FORCE_MIN_MAX "SS_TOUCH_IDLE_IX1_FORCE_MIN_MAX" +#define SS_IX1_LP_SENSE_MIN_MAX "SS_TOUCH_IDLE_IX1_SENSE_MIN_MAX" +#define SS_CX1_LP_FORCE_MIN_MAX "SS_TOUCH_IDLE_CX1_FORCE_MIN_MAX" +#define SS_CX1_LP_SENSE_MIN_MAX "SS_TOUCH_IDLE_CX1_SENSE_MIN_MAX" +#define SS_IX2_LP_FORCE_MAP_MIN "SS_TOUCH_IDLE_IX2_FORCE_MIN" +#define SS_IX2_LP_FORCE_MAP_MAX "SS_TOUCH_IDLE_IX2_FORCE_MAX" +#define SS_IX2_LP_SENSE_MAP_MIN "SS_TOUCH_IDLE_IX2_SENSE_MIN" +#define SS_IX2_LP_SENSE_MAP_MAX "SS_TOUCH_IDLE_IX2_SENSE_MAX" +#define SS_IX2_LP_FORCE_ADJV_MAP_MAX "SS_TOUCH_IDLE_IX2_ADJ_VERTICAL" +#define SS_IX2_LP_SENSE_ADJH_MAP_MAX "SS_TOUCH_IDLE_IX2_ADJ_HORIZONTAL" +#define SS_CX2_LP_FORCE_MAP_MIN "SS_TOUCH_IDLE_CX2_FORCE_MIN" +#define SS_CX2_LP_FORCE_MAP_MAX "SS_TOUCH_IDLE_CX2_FORCE_MAX" +#define SS_CX2_LP_SENSE_MAP_MIN "SS_TOUCH_IDLE_CX2_SENSE_MIN" +#define SS_CX2_LP_SENSE_MAP_MAX "SS_TOUCH_IDLE_CX2_SENSE_MAX" +#define SS_CX2_LP_FORCE_ADJV_MAP_MAX "SS_TOUCH_IDLE_CX2_ADJ_VERTICAL" +#define SS_CX2_LP_SENSE_ADJH_MAP_MAX "SS_TOUCH_IDLE_CX2_ADJ_HORIZONTAL" + + +/* TOTAL SS */ +#define SS_TOTAL_IX_LP_FORCE_MAP_MIN "SS_TOUCH_IDLE_TOTAL_IX_FORCE_MIN" +#define SS_TOTAL_IX_LP_FORCE_MAP_MAX "SS_TOUCH_IDLE_TOTAL_IX_FORCE_MAX" +#define SS_TOTAL_IX_LP_SENSE_MAP_MIN "SS_TOUCH_IDLE_TOTAL_IX_SENSE_MIN" +#define SS_TOTAL_IX_LP_SENSE_MAP_MAX "SS_TOUCH_IDLE_TOTAL_IX_SENSE_MAX" +#define SS_TOTAL_IX_LP_FORCE_ADJV_MAP_MAX \ + "SS_TOUCH_IDLE_TOTAL_IX_ADJ_VERTICAL" +#define SS_TOTAL_IX_LP_SENSE_ADJH_MAP_MAX \ + "SS_TOUCH_IDLE_TOTAL_IX_ADJ_HORIZONTAL" +#define SS_TOTAL_CX_LP_FORCE_MAP_MIN "SS_TOUCH_IDLE_TOTAL_CX_FORCE_MIN" +#define SS_TOTAL_CX_LP_FORCE_MAP_MAX "SS_TOUCH_IDLE_TOTAL_CX_FORCE_MAX" +#define SS_TOTAL_CX_LP_SENSE_MAP_MIN "SS_TOUCH_IDLE_TOTAL_CX_SENSE_MIN" +#define SS_TOTAL_CX_LP_SENSE_MAP_MAX "SS_TOUCH_IDLE_TOTAL_CX_SENSE_MAX" +#define SS_TOTAL_CX_LP_FORCE_ADJV_MAP_MAX \ + "SS_TOUCH_IDLE_TOTAL_CX_ADJ_VERTICAL" +#define SS_TOTAL_CX_LP_SENSE_ADJH_MAP_MAX \ + "SS_TOUCH_IDLE_TOTAL_CX_ADJ_HORIZONTAL" + +/* KEYS */ +#define MS_KEY_RAW_MIN_MAX "MS_KEY_RAW_DATA_MIN_MAX" +#define MS_KEY_CX1_MIN_MAX "MS_KEY_CX1_MIN_MAX" +#define MS_KEY_CX2_MAP_MIN "MS_KEY_CX2_MIN" +#define MS_KEY_CX2_MAP_MAX "MS_KEY_CX2_MAX" +#define MS_KEY_TOTAL_CX_MAP_MIN "MS_KEY_TOTAL_CX_MIN" +#define MS_KEY_TOTAL_CX_MAP_MAX "MS_KEY_TOTAL_CX_MAX" + +/* CONSTANT TOTAL IX */ +#define SS_IX1_FORCE_W "IX1_FORCE_W" +#define SS_IX2_FORCE_W "IX2_FORCE_W" +#define SS_IX1_SENSE_W "IX1_SENSE_W" +#define SS_IX2_SENSE_W "IX2_SENSE_W" +/** @}*/ + + +/** + * Struct used to specify which test perform during the Mass Production Test. + * For each test item selected in this structure, there should be one or + * more labels associated in the Limit file from where load the thresholds + */ +typedef struct { + int MutualRaw; /* /< MS Raw min/Max test */ + int MutualRawMap; /* /< MS Raw min/Max test for each node */ + int MutualRawGap; /* /< MS Raw Gap(max-min) test */ + int MutualRawAdj; /* /< MS Raw Adjacent test */ + int MutualRawAdjGap; /* /< MS Raw Adjacent Gap (max-min) test */ + int MutualRawAdjPeak; /* /< MS Raw Adjacent Peak + * max(max(adjv),max(adjh)) test + */ + int MutualRawLP; /* /< MS Low Power Raw min/Max test */ + int MutualRawMapLP; /* /< MS Low Power Raw min/Max test + * for each node + */ + int MutualRawGapLP; /* /< MS Low Power Raw Gap(max-min) test */ + int MutualRawAdjLP; /* /< MS Low Power Raw Adjacent test */ + int MutualRawAdjITO; /* /< MS Raw Adjacent test during ITO test */ + int MutualRawMapITO; /* /< MS Raw ITO min/Max test */ + + int MutualCx1; /* /< MS Cx1 min/Max test */ + int MutualCx2; /* /< MS Cx2 min/Max (for each node) test */ + int MutualCx2Adj; /* /< MS Vertical and Horizontal Adj Cx2 min/Max + * (for each node) test */ + int MutualCxTotal; /* /< MS Total Cx min/Max (for each node) test + * */ + int MutualCxTotalAdj; /* /< MS Total vertical and Horizontal Adj Cx2 + * min/Max (for each node) test + */ + + int MutualCx1LP; /* /< MS LowPower Cx1 min/Max test */ + int MutualCx2LP; /* /< MS LowPower Cx2 min/Max (for each node) + * test + */ + int MutualCx2AdjLP; /* /< MS LowPower Vertical and Horizontal Adj + * Cx2 min/Max + * (for each node) test + */ + int MutualCxTotalLP; /* /< MS Total LowPower Cx min/Max + * (for each node) test */ + int MutualCxTotalAdjLP; /* /< MS Total LowPower vertical and Horizontal + * Adj Cx2 min/Max (for each node) test + */ + + int MutualKeyRaw; /* /< MS Raw Key min/Max test */ + int MutualKeyCx1; /* /< MS Cx1 Key min/Max test */ + int MutualKeyCx2; /* /< MS Cx2 Key min/Max (for each node) test */ + int MutualKeyCxTotal; /* /< MS Total Cx Key min/Max (for each node) + * test */ + + int SelfForceRaw; /* /< SS Force Raw min/Max test */ + int SelfForceRawGap; /* /< SS Force Raw Gap(max-min) test */ + int SelfForceRawMap; /* /< SS Force Raw min/Max Map test */ + int SelfForceRawLP; /* /< SS Low Power Force Raw min/Max test */ + int SelfForceRawGapLP; /* /< SS Low Power Force Raw Gap(max-min) test */ + int SelfForceRawMapLP; /* /< SS Low Power Force Raw min/Max Map test */ + + int SelfForceIx1; /* /< SS Force Ix1 min/Max test */ + int SelfForceIx2; /* /< SS Force Ix2 min/Max (for each node) test + * */ + int SelfForceIx2Adj; /* /< SS Vertical Adj Force Ix2 min/Max + * (for each node) test */ + int SelfForceIxTotal; /* /< SS Total Force Ix min/Max (for each node) + * test */ + int SelfForceIxTotalAdj; /* /< SS Total Vertical Adj Force Ix + * min/Max + * (for each node) test */ + int SelfForceCx1; /* /< SS Force Cx1 min/Max test */ + int SelfForceCx2; /* /< SS Force Cx2 min/Max (for each node) test */ + int SelfForceCx2Adj; /* /< SS Vertical Adj Force Cx2 min/Max (for + * each node) test */ + int SelfForceCxTotal; /* /< SS Total Force Cx min/Max (for each node) + * test */ + int SelfForceCxTotalAdj; /* /< SS Total Vertical Adj Force Cx + * min/Max (for each node) test + */ + + int SelfForceIx1LP; /* /< SS LP Force Ix1 min/Max test */ + int SelfForceIx2LP; /* /< SS LP Force Ix2 min/Max (for each node) + * test + */ + int SelfForceIx2AdjLP; /* /< SS LP Vertical Adj Force Ix2 min/Max + * (for each node) test */ + int SelfForceIxTotalLP; /* /< SS LP Total Force Ix min/Max + * (for each node) test + */ + int SelfForceIxTotalAdjLP; /* /< SS LP Total Vertical Adj Force Ix + * min/Max (for each node) test + */ + int SelfForceCx1LP; /* /< SS LP Force Cx1 min/Max test */ + int SelfForceCx2LP; /* /< SS LP Force Cx2 min/Max (for each node) + * test + */ + int SelfForceCx2AdjLP; /* /< SS LP Vertical Adj Force Cx2 min/Max (for + * each node) test + */ + int SelfForceCxTotalLP; /* /< SS LP Total Force Cx min/Max + * (for each node) test + */ + int SelfForceCxTotalAdjLP; /* /< SS LP Total Vertical Adj Force Cx + * min/Max (for each node) test + */ + + int SelfSenseRaw; /* /< SS Sense Raw min/Max test */ + int SelfSenseRawGap; /* /< SS Sense Raw Gap(max-min) test */ + int SelfSenseRawMap; /* /< SS Sense Raw min/Max test for each node */ + int SelfSenseRawLP; /* /< SS Low Power Sense Raw min/Max test */ + int SelfSenseRawGapLP; /* /< SS Low Power Sense Raw Gap(max-min) test */ + int SelfSenseRawMapLP; /* /< SS Low Power Sense Raw min/Max test for + * each node + */ + + int SelfSenseIx1; /* /< SS Sense Ix1 min/Max test */ + int SelfSenseIx2; /* /< SS Sense Ix2 min/Max (for each node) test */ + int SelfSenseIx2Adj; /* /< SS Horizontal Adj Sense Ix2 min/Max + * (for each node) test */ + int SelfSenseIxTotal; /* /< SS Total Horizontal Sense Ix min/Max + * (for each node) test */ + int SelfSenseIxTotalAdj; /* /< SS Total Horizontal Adj Sense Ix + * min/Max + * (for each node) test */ + int SelfSenseCx1; /* /< SS Sense Cx1 min/Max test */ + int SelfSenseCx2; /* /< SS Sense Cx2 min/Max (for each node) test */ + int SelfSenseCx2Adj; /* /< SS Horizontal Adj Sense Cx2 min/Max + * (for each node) test */ + int SelfSenseCxTotal; /* /< SS Total Sense Cx min/Max (for each node) + * test */ + int SelfSenseCxTotalAdj; /* /< SS Total Horizontal Adj Sense Cx + * min/Max + * (for each node) test */ + int SelfSenseIx1LP; /* /< SS LP Sense Ix1 min/Max test */ + int SelfSenseIx2LP; /* /< SS LP Sense Ix2 min/Max (for each node) + * test + */ + int SelfSenseIx2AdjLP; /* /< SS LP Horizontal Adj Sense Ix2 min/Max + * (for each node) test + */ + int SelfSenseIxTotalLP; /* /< SS LP Total Horizontal Sense Ix min/Max + * (for each node) test + */ + int SelfSenseIxTotalAdjLP; /* /< SS LP Total Horizontal Adj Sense Ix + * min/Max (for each node) test + */ + int SelfSenseCx1LP; /* /< SS LP Sense Cx1 min/Max test */ + int SelfSenseCx2LP; /* /< SS LP Sense Cx2 min/Max (for each node) + * test + */ + int SelfSenseCx2AdjLP; /* /< SS LP Horizontal Adj Sense Cx2 min/Max + * (for each node) test + */ + int SelfSenseCxTotalLP; /* /< SS LP Total Sense Cx min/Max + * (for each node) test + */ + int SelfSenseCxTotalAdjLP; /* /< SS LP Total Horizontal Adj Sense Cx + * min/Max (for each node) test + */ +} TestToDo; + + +#define MAX_LIMIT_FILE_NAME 100 /* /< max number of chars of the limit file name + * */ + +/** + * Struct which store the data coming from a Production Limit File + */ +typedef struct { + char *data; /* /< pointer to an array of char which contains + * the content of the Production Limit File */ + int size; /* /< size of data */ + char name[MAX_LIMIT_FILE_NAME]; /* /< identifier of the source from + * where the limits data were loaded + * (if loaded from a file it will be + * the file name, while if loaded + * from .h will be "NULL") */ +} LimitFile; + + + +int initTestToDo(void); +/**@}*/ + +/**@}*/ + + +int computeAdjHoriz(i8 *data, int row, int column, u8 **result); +int computeAdjHorizTotal(short *data, int row, int column, u16 **result); +int computeAdjVert(i8 *data, int row, int column, u8 **result); +int computeAdjVertTotal(short *data, int row, int column, u16 **result); +int computeAdjHorizFromU(u8 *data, int row, int column, u8 **result); +int computeAdjHorizTotalFromU(u16 *data, int row, int column, u16 **result); +int computeAdjVertFromU(u8 *data, int row, int column, u8 **result); +int computeAdjVertTotalFromU(u16 *data, int row, int column, u16 **result); +int checkLimitsMinMax(short *data, int row, int column, int min, int max); +int checkLimitsMap(i8 *data, int row, int column, int *min, int *max); +int checkLimitsMapTotal(short *data, int row, int column, int *min, int *max); +int checkLimitsMapFromU(u8 *data, int row, int column, int *min, int *max); +int checkLimitsMapTotalFromU(u16 *data, int row, int column, int *min, + int *max); +int checkLimitsMapAdj(u8 *data, int row, int column, int *max); +int checkLimitsMapAdjTotal(u16 *data, int row, int column, int *max); +int checkLimitsGap(short *data, int row, int column, int threshold); +int checkLimitsGapOffsets(short *data, int row, int column, int threshold, + int row_start, int column_start, int row_end, int column_end); + +/** @defgroup mp_api MP API + * @ingroup mp_test + * Functions to execute the MP test. + * The parameters of these functions allow to customize their behavior + * in order to satisfy different scenarios + * @{ + */ +int production_test_ito(const char *path_limits, TestToDo *todo, + MutualSenseFrame *frame); +int production_test_initialization(u8 type); +int production_test_main(const char *pathThresholds, int stop_on_fail, + int saveInit, TestToDo *todo, u8 mpflag); +int production_test_ms_raw(const char *path_limits, int stop_on_fail, + TestToDo *todo); +int production_test_ms_raw_lp(const char *path_limits, int stop_on_fail, + TestToDo *todo); +int production_test_ms_cx(const char *path_limits, int stop_on_fail, + TestToDo *todo); +int production_test_ms_cx_lp(const char *path_limits, int stop_on_fail, + TestToDo *todo); +int production_test_ss_raw(const char *path_limits, int stop_on_fail, + TestToDo *todo); +int production_test_ss_raw_lp(const char *path_limits, int stop_on_fail, + TestToDo *todo); +int production_test_ss_ix_cx(const char *path_limits, int stop_on_fail, + TestToDo *todo); +int production_test_ss_ix_cx_lp(const char *path_limits, int stop_on_fail, + TestToDo *todo); +int production_test_data(const char *path_limits, int stop_on_fail, + TestToDo *todo); +int production_test_ms_key_cx(const char *path_limits, int stop_on_fail, + TestToDo *todo); +int production_test_ms_key_raw(const char *path_limits); +/** @}*/ + +/** + * @addtogroup limit_file + * @{ + */ +int parseProductionTestLimits(const char *path, LimitFile *file, char *label, + int **data, int *row, int *column); +int readLine(char *data, char *line, int size, int *n); +int getLimitsFile(const char *path, LimitFile *file); +int freeLimitsFile(LimitFile *file); +int freeCurrentLimitsFile(void); +/**@}*/ + +int tp_sensitivity_test_pre_cal_ms(MutualSenseFrame *finalFrame, short target, + int percentage); +int tp_sensitivity_test_pre_cal_ss(SelfSenseFrame *finalFrame, short target, + int percentage); +int tp_sensitivity_compute_gains(MutualSenseFrame *frame, short target, + int saveGain); +int tp_sensitivity_test_post_cal_ms(MutualSenseFrame *finalFrame, + MutualSenseFrame *deltas, short target, + int percentage, int *mean_normal, + int *mean_edge); +int tp_sensitivity_set_scan_mode(u8 scan, int enableGains); +int tp_sensitivity_mode(u8 enter, int saveGain); +int tp_sensitivity_test_std_ms(int numFrames, MutualSenseFrame *std); +#endif diff --git a/fts_lib/ftsTime.c b/fts_lib/ftsTime.c new file mode 100644 index 0000000..9ab18fd --- /dev/null +++ b/fts_lib/ftsTime.c @@ -0,0 +1,84 @@ +/* + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS Utility for mesuring/handling the time * + * * + ************************************************************************** + ************************************************************************** + * + */ + +/*! + * \file ftsTime.c + * \brief Contains all functions to handle and measure the time in the driver + */ + +#include "ftsTime.h" + + +#include <linux/errno.h> + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <stdarg.h> +#include <linux/time.h> +#include <linux/delay.h> +#include <linux/ctype.h> + + +/** + * Take the starting time and save it in a StopWatch variable + * @param w pointer of a StopWatch struct + */ +void startStopWatch(StopWatch *w) +{ + w->start = current_kernel_time(); +} + +/** + * Take the stop time and save it in a StopWatch variable + * @param w pointer of a StopWatch struct + */ +void stopStopWatch(StopWatch *w) +{ + w->end = current_kernel_time(); +} + +/** + * Compute the amount of time spent from when the startStopWatch and then + * the stopStopWatch were called on the StopWatch variable + * @param w pointer of a StopWatch struct + * @return amount of time in ms (the return value is meaningless + * if the startStopWatch and stopStopWatch were not called before) + */ +int elapsedMillisecond(StopWatch *w) +{ + int result; + + result = ((w->end.tv_sec - w->start.tv_sec) * 1000) + + (w->end.tv_nsec - w->start.tv_nsec) / 1000000; + return result; +} + +/** + * Compute the amount of time spent from when the startStopWatch and + * then the stopStopWatch were called on the StopWatch variable + * @param w pointer of a StopWatch struct + * @return amount of time in ns (the return value is meaningless + * if the startStopWatch and stopStopWatch were not called before) + */ +int elapsedNanosecond(StopWatch *w) +{ + int result; + + result = ((w->end.tv_sec - w->start.tv_sec) * 1000000000) + + (w->end.tv_nsec - w->start.tv_nsec); + return result; +} diff --git a/fts_lib/ftsTime.h b/fts_lib/ftsTime.h new file mode 100644 index 0000000..ffdf66a --- /dev/null +++ b/fts_lib/ftsTime.h @@ -0,0 +1,74 @@ +/* + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS Utility for measuring/handling the time * + * * + ************************************************************************** + ************************************************************************** + * + */ + +/*! + * \file ftsTime.h + * \brief Contains all the definitions and structs to handle and measure the + * time in the driver + */ + +#ifndef FTS_TIME_H +#define FTS_TIME_H + + +#include <linux/time.h> + +/* TIMEOUT */ +/** @defgroup timeouts Timeouts + * Definitions of all the Timeout used in several operations + * @{ + */ +#define TIMEOUT_RESOLUTION 50 +/* /< timeout resolution in ms (all timeout should be multiples of this unit) */ +#define GENERAL_TIMEOUT (15 * TIMEOUT_RESOLUTION) +/* /< general timeout in ms */ +#define RELEASE_INFO_TIMEOUT (2 * TIMEOUT_RESOLUTION) +/* /< timeout to request release info in ms */ + + +#define TIMEOUT_REQU_COMP_DATA (4 * TIMEOUT_RESOLUTION) +/* /< timeout to request compensation data in ms */ +#define TIMEOUT_REQU_DATA (8 * TIMEOUT_RESOLUTION) +/* /< timeout to request data in ms */ +#define TIMEOUT_ITO_TEST_RESULT (4 * TIMEOUT_RESOLUTION) +/* /< timeout to perform ito test in ms */ +#define TIMEOUT_INITIALIZATION_TEST_RESULT (5000 * TIMEOUT_RESOLUTION) +/* /< timeout to perform initialization test in ms */ +#define TIEMOUT_ECHO (50 * TIMEOUT_RESOLUTION) +/* /< timeout of the echo command,*/ +#define TIMEOUT_ECHO_FLUSH (TIMEOUT_RESOLUTION) +/* /< timeout of the flush echo command,*/ +#define TIMEOUT_ECHO_FPI (200 * TIMEOUT_RESOLUTION) +/* /< timeout of the Full panel Init echo command */ +#define TIMEOUT_ECHO_SINGLE_ENDED_SPECIAL_AUTOTUNE \ + (100 * TIMEOUT_RESOLUTION) +/** @}*/ + + +/** + * Struct used to measure the time elapsed between a starting and ending point. + */ +typedef struct { + struct timespec start; /* /< store the starting time */ + struct timespec end; /* /< store the finishing time */ +} StopWatch; + + +void startStopWatch(StopWatch *w); +void stopStopWatch(StopWatch *w); +int elapsedMillisecond(StopWatch *w); +int elapsedNanosecond(StopWatch *w); + +#endif diff --git a/fts_lib/ftsTool.c b/fts_lib/ftsTool.c new file mode 100644 index 0000000..6a3f7c5 --- /dev/null +++ b/fts_lib/ftsTool.c @@ -0,0 +1,798 @@ +/* + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS Utility Functions * + * * + ************************************************************************** + ************************************************************************** + * + */ + +/*! + * \file ftsTool.c + * \brief Contains all the functions to support common operation inside the + * driver + */ + +#include "ftsCompensation.h" +#include "ftsCore.h" +#include "ftsError.h" +#include "ftsHardware.h" +#include "ftsIO.h" +#include "ftsSoftware.h" +#include "ftsTime.h" +#include "ftsTool.h" +#include "../fts.h" /* needed for the tag define */ + + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <stdarg.h> +#include <linux/serio.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/ctype.h> + + +/** + * Print an array of byte in a HEX string and attach at the beginning a label. + * The function allocate memory that should be free outside the function itself + * @param label string to attach at the beginning + * @param buff pointer to the byte array that should be printed as HEX string + * @param count size of buff + * @param result pointer to the array of characters that compose the HEX final + * string + * @param size size of result + * @return pointer to the array of characters that compose the HEX string, + * (same address of result) + * @warning result MUST be allocated outside the function and should be + * big enough to contain the data converted as HEX! + */ +char *printHex(char *label, u8 *buff, int count, u8 *result, int size) +{ + int i, offset = 0; + + offset = scnprintf(result + offset, size - offset, "%s", label); + for (i = 0; i < count; i++) { + offset += + scnprintf(result + offset, + size - offset, "%02X ", buff[i]); + /* this append automatically a null terminator char */ + } + return result; +} + +/** + * Clear the FIFO from any event + * @return OK if success or an error code which specify the type of error + */ +int flushFIFO(void) +{ + int ret; + u8 sett = SPECIAL_FIFO_FLUSH; + + ret = writeSysCmd(SYS_CMD_SPECIAL, &sett, 1); /* flush the FIFO */ + if (ret < OK) { + pr_err("flushFIFO: ERROR %08X\n", ret); + return ret; + } + + pr_info("FIFO flushed!\n"); + return OK; +} + + + +/** + * Convert an array of bytes to an array of u16 taking two bytes at time, + * src has LSB first. + * @param src pointer to the source byte array + * @param src_length size of src + * @param dst pointer to the destination array. + * @return the final size of dst (half of the source) or ERROR_OP_NOT_ALLOW + * if the size of src is not multiple of 2. + */ +int u8ToU16n(u8 *src, int src_length, u16 *dst) +{ + int i, j; + + if (src_length % 2 != 0) + return ERROR_OP_NOT_ALLOW; + else { + j = 0; + for (i = 0; i < src_length; i += 2) { + dst[j] = ((src[i + 1] & 0x00FF) << 8) + + (src[i] & 0x00FF); + j++; + } + } + + return src_length / 2; +} + +/** + * Convert an array of 2 bytes to a u16, src has LSB first (little endian). + * @param src pointer to the source byte array + * @param dst pointer to the destination u16. + * @return OK + */ +int u8ToU16(u8 *src, u16 *dst) +{ + *dst = (u16)(((src[1] & 0x00FF) << 8) + (src[0] & 0x00FF)); + return OK; +} + +/** + * Convert an array of 2 bytes to a u16, src has MSB first (big endian). + * @param src pointer to the source byte array + * @param dst pointer to the destination u16. + * @return OK + */ +int u8ToU16_be(u8 *src, u16 *dst) +{ + *dst = (u16)(((src[0] & 0x00FF) << 8) + (src[1] & 0x00FF)); + return OK; +} + +/** + * Convert an array of u16 to an array of u8, dst has MSB first (big endian). + * @param src pointer to the source array of u16 + * @param src_length size of src + * @param dst pointer to the destination array of u8. + * @return size of dst (src size multiply by 2) + */ +int u16ToU8n_be(u16 *src, int src_length, u8 *dst) +{ + int i, j = 0; + + for (i = 0; i < src_length; i++) { + dst[j] = (u8)(src[i] & 0xFF00) >> 8; + dst[j + 1] = (u8)(src[i] & 0x00FF); + j += 2; + } + + return src_length * 2; +} + +/** + * Convert a u16 to an array of 2 u8, dst has MSB first (big endian). + * @param src u16 to convert + * @param dst pointer to the destination array of 2 u8. + * @return OK + */ +int u16ToU8_be(u16 src, u8 *dst) +{ + dst[0] = (u8)((src & 0xFF00) >> 8); + dst[1] = (u8)(src & 0x00FF); + return OK; +} + + +/** + * Convert a u16 to an array of 2 u8, dst has LSB first (little endian). + * @param src u16 to convert + * @param dst pointer to the destination array of 2 u8. + * @return OK + */ +int u16ToU8(u16 src, u8 *dst) +{ + dst[1] = (u8)((src & 0xFF00) >> 8); + dst[0] = (u8)(src & 0x00FF); + return OK; +} + +/** + * Convert an array of bytes to a u32, src has LSB first (little endian). + * @param src array of bytes to convert + * @param dst pointer to the destination u32 variable. + * @return OK + */ +int u8ToU32(u8 *src, u32 *dst) +{ + *dst = (u32)(((src[3] & 0xFF) << 24) + ((src[2] & 0xFF) << 16) + + ((src[1] & 0xFF) << 8) + (src[0] & 0xFF)); + return OK; +} + +/** + * Convert an array of bytes to a u32, src has MSB first (big endian). + * @param src array of bytes to convert + * @param dst pointer to the destination u32 variable. + * @return OK + */ +int u8ToU32_be(u8 *src, u32 *dst) +{ + *dst = (u32)(((src[0] & 0xFF) << 24) + ((src[1] & 0xFF) << 16) + + ((src[2] & 0xFF) << 8) + (src[3] & 0xFF)); + return OK; +} + + +/** + * Convert a u32 to an array of 4 bytes, dst has LSB first (little endian). + * @param src u32 value to convert + * @param dst pointer to the destination array of 4 bytes. + * @return OK + */ +int u32ToU8(u32 src, u8 *dst) +{ + dst[3] = (u8)((src & 0xFF000000) >> 24); + dst[2] = (u8)((src & 0x00FF0000) >> 16); + dst[1] = (u8)((src & 0x0000FF00) >> 8); + dst[0] = (u8)(src & 0x000000FF); + return OK; +} + +/** + * Convert a u32 to an array of 4 bytes, dst has MSB first (big endian). + * @param src u32 value to convert + * @param dst pointer to the destination array of 4 bytes. + * @return OK + */ +int u32ToU8_be(u32 src, u8 *dst) +{ + dst[0] = (u8)((src & 0xFF000000) >> 24); + dst[1] = (u8)((src & 0x00FF0000) >> 16); + dst[2] = (u8)((src & 0x0000FF00) >> 8); + dst[3] = (u8)(src & 0x000000FF); + return OK; +} + +/** + * Execute a function passed as argment and retry it defined number of times if + * not successful + * @param code pointer to a function which return an int and doesn't have any + * parameters + * @param wait_before_retry interval of time in ms to wait between one trial + * and another one + * @param retry_count max number of retry to attemp + * @return last return value obtained from the last execution of the code + *function + */ +int attempt_function(int (*code)(void), unsigned long wait_before_retry, int + retry_count) +{ + int result; + int count = 0; + + do { + result = code(); + count++; + mdelay(wait_before_retry); + } while (count < retry_count && result < 0); + + + if (count == retry_count) + return result | ERROR_TIMEOUT; + else + return result; +} + +/** + * Enable all the possible sensing mode supported by the FW + * @return OK if success or an error code which specify the type of error + */ +int senseOn(void) +{ + int ret; + + ret = setScanMode(SCAN_MODE_ACTIVE, 0xFF); /* enable all */ + if (ret < OK) { + pr_err("senseOn: ERROR %08X\n", ret); + return ret; + } + + pr_info("senseOn: SENSE ON\n"); + return OK; +} + +/** + * Disable all the sensing mode + * @return OK if success or an error code which specify the type of error + */ +int senseOff(void) +{ + int ret; + + ret = setScanMode(SCAN_MODE_ACTIVE, 0x00); + if (ret < OK) { + pr_err("senseOff: ERROR %08X\n", ret); + return ret; + } + + pr_info("senseOff: SENSE OFF\n"); + return OK; +} + + + +/** + * Clean up the IC status executing a system reset and giving + * the possibility to re-enabling the sensing + * @param enableTouch if 1, re-enable the sensing and the interrupt of the IC + * @return OK if success or an error code which specify the type of error + */ +int cleanUp(int enableTouch) +{ + int res; + + pr_info("cleanUp: system reset...\n"); + res = fts_system_reset(); + if (res < OK) + return res; + if (enableTouch) { + pr_info("cleanUp: enabling touches...\n"); + res = senseOn(); /* already enable everything */ + if (res < OK) + return res; + pr_info("cleanUp: enabling interrupts...\n"); + res = fts_enableInterrupt(true); + if (res < OK) + return res; + } + return OK; +} + +/** + * Transform an array of short in a matrix of short with a defined number of + * columns and the resulting number of rows + * @param data array of bytes to convert + * @param size size of data + * @param columns number of columns that the resulting matrix should have. + * @return a reference to a matrix of short where for each row there are + * columns elements + */ +short **array1dTo2d_short(short *data, int size, int columns) +{ + int i; + short **matrix = NULL; + + if (size != 0) + matrix = (short **)kmalloc_array(((int)(size / columns)), + sizeof(short *), GFP_KERNEL); + + if (matrix != NULL) { + for (i = 0; i < (int)(size / columns); i++) { + matrix[i] = (short *)kmalloc_array(columns, + sizeof(short), GFP_KERNEL); + if (!matrix[i]) + break; + } + + for (i = 0; i < size; i++) { + if (!matrix[i / columns]) + break; + matrix[i / columns][i % columns] = data[i]; + } + } + + return matrix; +} + +/** + * Transform an array of u16 in a matrix of u16 with a defined number of + * columns and the resulting number of rows + * @param data array of bytes to convert + * @param size size of data + * @param columns number of columns that the resulting matrix should have. + * @return a reference to a matrix of u16 where for each row there are columns + * elements + */ +u16 **array1dTo2d_u16(u16 *data, int size, int columns) +{ + int i; + u16 **matrix = NULL; + + if (size != 0) + matrix = (u16 **)kmalloc_array(((int)(size / columns)), + sizeof(u16 *), GFP_KERNEL); + + if (matrix != NULL) { + for (i = 0; i < (int)(size / columns); i++) { + matrix[i] = (u16 *)kmalloc_array(columns, + sizeof(u16), GFP_KERNEL); + if (!matrix[i]) + break; + } + + for (i = 0; i < size; i++) { + if (!matrix[i / columns]) + break; + matrix[i / columns][i % columns] = data[i]; + } + } + + return matrix; +} + +/** + * Transform an array of u8 in a matrix of u8 with a defined number of + * columns and the resulting number of rows + * @param data array of bytes to convert + * @param size size of data + * @param columns number of columns that the resulting matrix should have. + * @return a reference to a matrix of short where for each row there are + * columns elements + */ +u8 **array1dTo2d_u8(u8 *data, int size, int columns) +{ + int i; + u8 **matrix = NULL; + + if (size != 0) { + matrix = (u8 **)kmalloc_array(((int)(size / columns)), + sizeof(u8 *), GFP_KERNEL); + } + + if (matrix != NULL) { + for (i = 0; i < (int)(size / columns); i++) { + matrix[i] = (u8 *)kmalloc_array(columns, + sizeof(u8), GFP_KERNEL); + if (!matrix[i]) + break; + } + + for (i = 0; i < size; i++) { + if (!matrix[i / columns]) + break; + matrix[i / columns][i % columns] = data[i]; + } + } + + return matrix; +} + +/** + * Transform an array of i8 in a matrix of i8 with a defined number of + * columns and the resulting number of rows + * @param data array of bytes to convert + * @param size size of data + * @param columns number of columns that the resulting matrix should have. + * @return a reference to a matrix of short where for each row there are + * columns elements + */ +i8 **array1dTo2d_i8(i8 *data, int size, int columns) +{ + int i; + i8 **matrix = NULL; + + if (size != 0) + matrix = (i8 **)kmalloc_array(((int)(size / columns)), + sizeof(i8 *), GFP_KERNEL); + + if (matrix != NULL) { + for (i = 0; i < (int)(size / columns); i++) { + matrix[i] = (i8 *)kmalloc_array(columns, + sizeof(i8), GFP_KERNEL); + if (!matrix[i]) + break; + } + + for (i = 0; i < size; i++) { + if (!matrix[i / columns]) + break; + matrix[i / columns][i % columns] = data[i]; + } + } + + return matrix; +} + +/** + * Print in the kernel log a label followed by a matrix of short row x columns + * and free its memory + * @param label pointer to the string to print before the actual matrix + * @param matrix reference to the matrix of short which contain the actual data + * @param row number of rows on which the matrix should be print + * @param column number of columns for each row + */ +void print_frame_short(char *label, short **matrix, int row, int column) +{ + int i, j; + int buff_len, index; + char *buff; + + pr_info("%s\n", label); + + if (matrix == NULL) + return; + + buff_len = (6 + 1) * column + 1; /* -32768 str len: 6 */ + buff = kzalloc(buff_len, GFP_KERNEL); + if (buff == NULL) { + pr_err("%s: fail to allocate buffer\n", __func__); + return; + } + + for (i = 0; i < row; i++) { + if (!matrix[i]) + break; + index = 0; + for (j = 0; j < column; j++) + index += scnprintf(buff + index, buff_len - index, + "%d ", matrix[i][j]); + pr_info("%s\n", buff); + kfree(matrix[i]); + } + kfree(matrix); + kfree(buff); +} + +/** + * Print in the kernel log a label followed by a matrix of u16 row x columns + * and free its memory + * @param label pointer to the string to print before the actual matrix + * @param matrix reference to the matrix of u16 which contain the actual data + * @param row number of rows on which the matrix should be print + * @param column number of columns for each row + */ +void print_frame_u16(char *label, u16 **matrix, int row, int column) +{ + int i, j; + int buff_len, index; + char *buff; + + pr_info("%s\n", label); + + if (matrix == NULL) + return; + + buff_len = (5 + 1) * column + 1; /* 65535 str len: 5 */ + buff = kzalloc(buff_len, GFP_KERNEL); + if (buff == NULL) { + pr_err("%s: fail to allocate buffer\n", __func__); + return; + } + + for (i = 0; i < row; i++) { + if (!matrix[i]) + break; + index = 0; + for (j = 0; j < column; j++) + index += scnprintf(buff + index, buff_len - index, + "%d ", matrix[i][j]); + pr_info("%s\n", buff); + kfree(matrix[i]); + } + kfree(matrix); + kfree(buff); +} + +/** + * Print in the kernel log a label followed by a matrix of u8 row x columns and + * free its memory + * @param label pointer to the string to print before the actual matrix + * @param matrix reference to the matrix of u8 which contain the actual data + * @param row number of rows on which the matrix should be print + * @param column number of columns for each row + */ +void print_frame_u8(char *label, u8 **matrix, int row, int column) +{ + int i, j; + int buff_len, index; + char *buff; + + pr_info("%s\n", label); + + if (matrix == NULL) + return; + + buff_len = (3 + 1) * column + 1; /* 255 str len: 3 */ + buff = kzalloc(buff_len, GFP_KERNEL); + if (buff == NULL) { + pr_err("%s: fail to allocate buffer\n", __func__); + return; + } + + for (i = 0; i < row; i++) { + if (!matrix[i]) + break; + index = 0; + for (j = 0; j < column; j++) + index += scnprintf(buff + index, buff_len - index, + "%d ", matrix[i][j]); + pr_info("%s\n", buff); + kfree(matrix[i]); + } + kfree(matrix); + kfree(buff); +} + +/** + * Print in the kernel log a label followed by a matrix of i8 row x columns and + * free its memory + * @param label pointer to the string to print before the actual matrix + * @param matrix reference to the matrix of u8 which contain the actual data + * @param row number of rows on which the matrix should be print + * @param column number of columns for each row + */ +void print_frame_i8(char *label, i8 **matrix, int row, int column) +{ + int i, j; + int buff_len, index; + char *buff; + + pr_info("%s\n", label); + + if (matrix == NULL) + return; + + buff_len = (4 + 1) * column + 1; /* -128 str len: 4 */ + buff = kzalloc(buff_len, GFP_KERNEL); + if (buff == NULL) { + pr_err("%s: fail to allocate buffer\n", __func__); + return; + } + + for (i = 0; i < row; i++) { + if (!matrix[i]) + break; + index = 0; + for (j = 0; j < column; j++) + index += scnprintf(buff + index, buff_len - index, + "%d ", matrix[i][j]); + pr_info("%s\n", buff); + kfree(matrix[i]); + } + kfree(matrix); + kfree(buff); +} + +/** + * Print in the kernel log a label followed by a matrix of u32 row x columns + * and free its memory + * @param label pointer to the string to print before the actual matrix + * @param matrix reference to the matrix of u32 which contain the actual data + * @param row number of rows on which the matrix should be print + * @param column number of columns for each row + */ +void print_frame_u32(char *label, u32 **matrix, int row, int column) +{ + int i, j; + int buff_len, index; + char *buff; + + pr_info("%s\n", label); + + if (matrix == NULL) + return; + + buff_len = (10 + 1) * column + 1; /* 4294967295 str len: 10 */ + buff = kzalloc(buff_len, GFP_KERNEL); + if (buff == NULL) { + pr_err("%s: fail to allocate buffer\n", __func__); + return; + } + + for (i = 0; i < row; i++) { + if (!matrix[i]) + break; + index = 0; + for (j = 0; j < column; j++) + index += scnprintf(buff + index, buff_len - index, + "%d ", matrix[i][j]); + pr_info("%s\n", buff); + kfree(matrix[i]); + } + kfree(matrix); + kfree(buff); +} + +/** + * Print in the kernel log a label followed by a matrix of int row x columns + * and free its memory + * @param label pointer to the string to print before the actual matrix + * @param matrix reference to the matrix of int which contain the actual data + * @param row number of rows on which the matrix should be print + * @param column number of columns for each row + */ +void print_frame_int(char *label, int **matrix, int row, int column) +{ + int i, j; + int buff_len, index; + char *buff; + + pr_info("%s\n", label); + + if (matrix == NULL) + return; + + buff_len = (11 + 1) * column + 1; /* -2147483648 str len: 11 */ + buff = kzalloc(buff_len, GFP_KERNEL); + if (buff == NULL) { + pr_err("%s: fail to allocate buffer\n", __func__); + return; + } + + for (i = 0; i < row; i++) { + if (!matrix[i]) + break; + index = 0; + for (j = 0; j < column; j++) + index += scnprintf(buff + index, buff_len - index, + "%d ", matrix[i][j]); + pr_info("%s\n", buff); + kfree(matrix[i]); + } + kfree(matrix); + kfree(buff); +} + + +/** + * Convert an array of bytes to an u64, src has MSB first (big endian). + * @param src array of bytes + * @param dest pointer to the destination u64. + * @param size size of src (can be <= 8) + * @return OK if success or ERROR_OP_NOT_ALLOW if size exceed 8 + */ +int u8ToU64_be(u8 *src, u64 *dest, int size) +{ + int i = 0; + + /* u64 temp =0; */ + if (size > sizeof(u64)) + return ERROR_OP_NOT_ALLOW; + + *dest = 0; + for (i = 0; i < size; i++) + *dest |= (u64)(src[i]) << ((size - 1 - i) * 8); + + return OK; +} + +/** + * Convert an u64 to an array of bytes, dest has MSB first (big endian). + * @param src value of u64 + * @param dest pointer to the destination array of bytes. + * @param size size of src (can be <= 8) + * @return OK if success or ERROR_OP_NOT_ALLOW if size exceed 8 + */ +int u64ToU8_be(u64 src, u8 *dest, int size) +{ + int i = 0; + + if (size > sizeof(u64)) + return ERROR_OP_NOT_ALLOW; + else + for (i = 0; i < size; i++) + dest[i] = (u8)((src >> ((size - 1 - i) * 8)) & 0xFF); + + return OK; +} + + + +/*********** NEW API *************/ + +/** + * Convert a value of an id in a bitmask with a 1 in the position of the value + * of the id + * @param id Value of the ID to convert + * @param mask pointer to the bitmask that will be updated with the value of id + * @param size dimension in bytes of mask + * @return OK if success or ERROR_OP_NOT_ALLOW if size of mask is not enough to + * contain ID + */ +int fromIDtoMask(u8 id, u8 *mask, int size) +{ + if (((int)((id) / 8)) < size) { + pr_info("%s: ID = %d Index = %d Position = %d !\n", + __func__, id, ((int)((id) / 8)), (id % 8)); + mask[((int)((id) / 8))] |= 0x01 << (id % 8); + return OK; + } else { + pr_err("%s: Bitmask too small! Impossible contain ID = %d %d>=%d! ERROR %08X\n", + __func__, id, ((int)((id) / 8)), size, + ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } +} diff --git a/fts_lib/ftsTool.h b/fts_lib/ftsTool.h new file mode 100644 index 0000000..284ef6c --- /dev/null +++ b/fts_lib/ftsTool.h @@ -0,0 +1,58 @@ +/* + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS Utility Functions * + * * + ************************************************************************** + ************************************************************************** + * + */ + +/*! + * \file ftsTool.h + * \brief Contains all the definitions to support common operations inside the + * driver + */ + +#ifndef FTS_TOOL_H +#define FTS_TOOL_H + +char *printHex(char *label, u8 *buff, int count, u8 *result, int size); +int u8ToU16(u8 *src, u16 *dst); +int u8ToU16_be(u8 *src, u16 *dst); +int u8ToU16n(u8 *src, int src_length, u16 *dst); +int u16ToU8(u16 src, u8 *dst); +int u16ToU8_be(u16 src, u8 *dst); +int u16ToU8n_be(u16 *src, int src_length, u8 *dst); +int u8ToU32(u8 *src, u32 *dst); +int u8ToU32_be(u8 *src, u32 *dst); +int u32ToU8(u32 src, u8 *dst); +int u32ToU8_be(u32 src, u8 *dst); +int u8ToU64_be(u8 *src, u64 *dest, int size); +int u64ToU8_be(u64 src, u8 *dest, int size); +int attempt_function(int (*code)(void), unsigned long wait_before_retry, int + retry_count); +int senseOn(void); +int senseOff(void); +void print_frame_short(char *label, short **matrix, int row, int column); +short **array1dTo2d_short(short *data, int size, int columns); +void print_frame_u16(char *label, u16 **matrix, int row, int column); +u16 **array1dTo2d_u16(u16 *data, int size, int columns); +u8 **array1dTo2d_u8(u8 *data, int size, int columns); +i8 **array1dTo2d_i8(i8 *data, int size, int columns); +void print_frame_u8(char *label, u8 **matrix, int row, int column); +void print_frame_i8(char *label, i8 **matrix, int row, int column); +void print_frame_u32(char *label, u32 **matrix, int row, int column); +void print_frame_int(char *label, int **matrix, int row, int column); +int cleanUp(int enableTouch); +int flushFIFO(void); + +/* New API */ +int fromIDtoMask(u8 id, u8 *mask, int size); + +#endif diff --git a/fts_limits.h b/fts_limits.h new file mode 100644 index 0000000..35eed02 --- /dev/null +++ b/fts_limits.h @@ -0,0 +1,2045 @@ +#ifndef FTS_LIMITS_H +#define FTS_LIMITS_H +// This is an auto generated header file +//--->Remember to change the name of the two variables!<--- +const uint32_t myArray2_size = 24418; + +const uint8_t myArray2[] = { + 0x2A, 0x46, 0x4F, 0x52, 0x43, 0x45, 0x5F, 0x4C, 0x45, 0x4E, 0x47, 0x54, + 0x48, 0x2C, 0x31, 0x2C, 0x31, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x0D, 0x0A, 0x31, 0x37, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x2A, 0x53, 0x45, 0x4E, 0x53, + 0x45, 0x5F, 0x4C, 0x45, 0x4E, 0x47, 0x54, 0x48, 0x2C, 0x31, 0x2C, 0x31, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x33, + 0x36, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x0D, 0x0A, 0x2A, 0x4B, 0x45, 0x59, 0x5F, 0x4C, 0x45, 0x4E, 0x47, 0x54, + 0x48, 0x2C, 0x31, 0x2C, 0x31, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x0D, 0x0A, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x2A, 0x53, 0x53, 0x4B, 0x45, 0x59, + 0x5F, 0x4C, 0x45, 0x4E, 0x47, 0x54, 0x48, 0x2C, 0x31, 0x2C, 0x31, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x30, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, + 0x2A, 0x45, 0x4E, 0x5F, 0x4C, 0x50, 0x5F, 0x54, 0x49, 0x4D, 0x45, 0x52, + 0x5F, 0x43, 0x41, 0x4C, 0x49, 0x42, 0x52, 0x41, 0x54, 0x49, 0x4F, 0x4E, + 0x2C, 0x31, 0x2C, 0x31, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x0D, 0x0A, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x2A, 0x45, 0x4E, 0x5F, 0x4D, 0x55, 0x54, + 0x55, 0x41, 0x4C, 0x5F, 0x41, 0x55, 0x54, 0x4F, 0x54, 0x55, 0x4E, 0x45, + 0x2C, 0x31, 0x2C, 0x31, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x0D, 0x0A, 0x31, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x2A, 0x45, 0x4E, 0x5F, 0x53, 0x45, 0x4C, + 0x46, 0x5F, 0x41, 0x55, 0x54, 0x4F, 0x54, 0x55, 0x4E, 0x45, 0x2C, 0x31, + 0x2C, 0x31, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, + 0x0A, 0x31, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x0D, 0x0A, 0x2A, 0x45, 0x4E, 0x5F, 0x53, 0x41, 0x56, 0x45, 0x5F, + 0x43, 0x58, 0x2C, 0x31, 0x2C, 0x31, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x2A, 0x43, 0x58, 0x31, 0x5F, + 0x57, 0x2C, 0x31, 0x2C, 0x31, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x0D, 0x0A, 0x33, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x2A, 0x43, 0x58, 0x32, 0x5F, + 0x57, 0x2C, 0x31, 0x2C, 0x31, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x0D, 0x0A, 0x34, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x2A, 0x49, 0x58, 0x31, 0x5F, 0x46, + 0x4F, 0x52, 0x43, 0x45, 0x5F, 0x57, 0x2C, 0x31, 0x2C, 0x31, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x32, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x2A, + 0x49, 0x58, 0x32, 0x5F, 0x46, 0x4F, 0x52, 0x43, 0x45, 0x5F, 0x57, 0x2C, + 0x31, 0x2C, 0x31, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x0D, 0x0A, 0x31, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x0D, 0x0A, 0x2A, 0x49, 0x58, 0x31, 0x5F, 0x53, 0x45, 0x4E, + 0x53, 0x45, 0x5F, 0x57, 0x2C, 0x31, 0x2C, 0x31, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x32, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x2A, 0x49, 0x58, + 0x32, 0x5F, 0x53, 0x45, 0x4E, 0x53, 0x45, 0x5F, 0x57, 0x2C, 0x31, 0x2C, + 0x31, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, + 0x31, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x0D, 0x0A, 0x2A, 0x4D, 0x53, 0x5F, 0x52, 0x41, 0x57, 0x5F, 0x44, 0x41, + 0x54, 0x41, 0x5F, 0x4D, 0x49, 0x4E, 0x5F, 0x4D, 0x41, 0x58, 0x2C, 0x31, + 0x2C, 0x32, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, + 0x0A, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x2A, + 0x4D, 0x53, 0x5F, 0x52, 0x41, 0x57, 0x5F, 0x44, 0x41, 0x54, 0x41, 0x5F, + 0x45, 0x41, 0x43, 0x48, 0x5F, 0x4D, 0x49, 0x4E, 0x2C, 0x31, 0x37, 0x2C, + 0x33, 0x36, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, + 0x0A, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x0D, 0x0A, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x0D, 0x0A, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x0D, 0x0A, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x0D, 0x0A, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x0D, 0x0A, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x0D, 0x0A, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x0D, 0x0A, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x0D, 0x0A, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x0D, 0x0A, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x0D, 0x0A, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x0D, 0x0A, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x0D, + 0x0A, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, + 0x0D, 0x0A, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, + 0x30, 0x0D, 0x0A, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, + 0x30, 0x30, 0x0D, 0x0A, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, + 0x30, 0x30, 0x30, 0x0D, 0x0A, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x2D, + 0x32, 0x30, 0x30, 0x30, 0x0D, 0x0A, 0x2A, 0x4D, 0x53, 0x5F, 0x52, 0x41, + 0x57, 0x5F, 0x44, 0x41, 0x54, 0x41, 0x5F, 0x45, 0x41, 0x43, 0x48, 0x5F, + 0x4D, 0x41, 0x58, 0x2C, 0x31, 0x37, 0x2C, 0x33, 0x36, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x0D, 0x0A, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x0D, 0x0A, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x0D, 0x0A, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x0D, 0x0A, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x0D, + 0x0A, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x0D, 0x0A, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x0D, 0x0A, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x0D, 0x0A, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x0D, 0x0A, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x0D, 0x0A, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x0D, 0x0A, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x0D, 0x0A, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x0D, 0x0A, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x0D, 0x0A, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x0D, 0x0A, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x0D, 0x0A, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, + 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, + 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, + 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, + 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x2C, 0x32, 0x30, 0x30, 0x30, 0x0D, + 0x0A, 0x2A, 0x4D, 0x53, 0x5F, 0x52, 0x41, 0x57, 0x5F, 0x49, 0x54, 0x4F, + 0x5F, 0x44, 0x41, 0x54, 0x41, 0x5F, 0x41, 0x44, 0x4A, 0x5F, 0x48, 0x4F, + 0x52, 0x49, 0x5A, 0x4F, 0x4E, 0x54, 0x41, 0x4C, 0x2C, 0x31, 0x37, 0x2C, + 0x33, 0x35, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, + 0x0A, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x0D, 0x0A, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x0D, 0x0A, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x0D, 0x0A, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x0D, 0x0A, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x0D, 0x0A, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x0D, + 0x0A, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x0D, 0x0A, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x0D, 0x0A, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x0D, 0x0A, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x0D, 0x0A, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x0D, 0x0A, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x0D, + 0x0A, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x0D, 0x0A, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x0D, 0x0A, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x0D, 0x0A, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x0D, 0x0A, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x0D, 0x0A, 0x2A, 0x4D, 0x53, 0x5F, 0x52, 0x41, 0x57, 0x5F, 0x49, + 0x54, 0x4F, 0x5F, 0x44, 0x41, 0x54, 0x41, 0x5F, 0x41, 0x44, 0x4A, 0x5F, + 0x56, 0x45, 0x52, 0x54, 0x49, 0x43, 0x41, 0x4C, 0x2C, 0x31, 0x36, 0x2C, + 0x33, 0x36, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, + 0x0A, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x0D, 0x0A, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x0D, 0x0A, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x0D, 0x0A, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x0D, 0x0A, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x0D, 0x0A, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x0D, 0x0A, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x0D, 0x0A, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x0D, 0x0A, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x0D, 0x0A, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x0D, 0x0A, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x0D, 0x0A, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x0D, + 0x0A, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x0D, 0x0A, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, + 0x30, 0x0D, 0x0A, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, + 0x30, 0x30, 0x0D, 0x0A, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, + 0x33, 0x30, 0x30, 0x0D, 0x0A, 0x2A, 0x4D, 0x53, 0x5F, 0x53, 0x54, 0x52, + 0x45, 0x4E, 0x47, 0x54, 0x48, 0x5F, 0x44, 0x41, 0x54, 0x41, 0x5F, 0x4D, + 0x49, 0x4E, 0x5F, 0x4D, 0x41, 0x58, 0x2C, 0x31, 0x2C, 0x32, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x2D, 0x32, 0x30, + 0x30, 0x2C, 0x32, 0x30, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x2A, 0x4D, 0x53, 0x5F, 0x54, 0x4F, 0x55, + 0x43, 0x48, 0x5F, 0x4C, 0x4F, 0x57, 0x50, 0x4F, 0x57, 0x45, 0x52, 0x5F, + 0x43, 0x58, 0x32, 0x5F, 0x4D, 0x49, 0x4E, 0x2C, 0x31, 0x37, 0x2C, 0x33, + 0x36, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x0D, + 0x0A, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x0D, 0x0A, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x0D, 0x0A, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x0D, 0x0A, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x0D, 0x0A, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x0D, 0x0A, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x0D, 0x0A, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x0D, 0x0A, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x0D, 0x0A, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x0D, 0x0A, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x0D, 0x0A, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x0D, 0x0A, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x0D, + 0x0A, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x0D, 0x0A, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x0D, 0x0A, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, + 0x2C, 0x35, 0x0D, 0x0A, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x35, 0x2C, + 0x35, 0x2C, 0x35, 0x0D, 0x0A, 0x2A, 0x4D, 0x53, 0x5F, 0x54, 0x4F, 0x55, + 0x43, 0x48, 0x5F, 0x4C, 0x4F, 0x57, 0x50, 0x4F, 0x57, 0x45, 0x52, 0x5F, + 0x43, 0x58, 0x32, 0x5F, 0x4D, 0x41, 0x58, 0x2C, 0x31, 0x37, 0x2C, 0x33, + 0x36, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x0D, + 0x0A, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x0D, 0x0A, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x0D, 0x0A, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x0D, 0x0A, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x0D, 0x0A, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x0D, 0x0A, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x0D, 0x0A, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x0D, 0x0A, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x0D, 0x0A, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x0D, 0x0A, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x0D, 0x0A, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x0D, 0x0A, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x0D, + 0x0A, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x0D, 0x0A, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, + 0x30, 0x0D, 0x0A, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, + 0x34, 0x30, 0x0D, 0x0A, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, 0x2C, 0x34, 0x30, + 0x2C, 0x34, 0x30, 0x0D, 0x0A, 0x2A, 0x4D, 0x53, 0x5F, 0x54, 0x4F, 0x55, + 0x43, 0x48, 0x5F, 0x4C, 0x4F, 0x57, 0x50, 0x4F, 0x57, 0x45, 0x52, 0x5F, + 0x43, 0x58, 0x32, 0x5F, 0x41, 0x44, 0x4A, 0x5F, 0x48, 0x4F, 0x52, 0x49, + 0x5A, 0x4F, 0x4E, 0x54, 0x41, 0x4C, 0x2C, 0x31, 0x37, 0x2C, 0x33, 0x35, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x0D, 0x0A, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x0D, 0x0A, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x0D, 0x0A, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x0D, 0x0A, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x0D, 0x0A, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x0D, 0x0A, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x0D, 0x0A, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x0D, 0x0A, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x0D, 0x0A, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x0D, + 0x0A, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x0D, 0x0A, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x0D, 0x0A, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x0D, 0x0A, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x0D, 0x0A, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x0D, 0x0A, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x0D, 0x0A, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x0D, 0x0A, 0x2A, 0x4D, 0x53, 0x5F, 0x54, 0x4F, + 0x55, 0x43, 0x48, 0x5F, 0x4C, 0x4F, 0x57, 0x50, 0x4F, 0x57, 0x45, 0x52, + 0x5F, 0x43, 0x58, 0x32, 0x5F, 0x41, 0x44, 0x4A, 0x5F, 0x56, 0x45, 0x52, + 0x54, 0x49, 0x43, 0x41, 0x4C, 0x2C, 0x31, 0x36, 0x2C, 0x33, 0x36, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x0D, 0x0A, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x0D, 0x0A, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x0D, + 0x0A, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x0D, 0x0A, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x0D, 0x0A, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x0D, 0x0A, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x0D, 0x0A, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x0D, 0x0A, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x0D, 0x0A, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x0D, 0x0A, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x0D, 0x0A, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x0D, 0x0A, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x0D, 0x0A, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, + 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x0D, 0x0A, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, + 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x0D, + 0x0A, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, 0x2C, 0x31, 0x30, + 0x0D, 0x0A, 0x2A, 0x53, 0x53, 0x5F, 0x52, 0x41, 0x57, 0x5F, 0x44, 0x41, + 0x54, 0x41, 0x5F, 0x46, 0x4F, 0x52, 0x43, 0x45, 0x5F, 0x4D, 0x49, 0x4E, + 0x5F, 0x4D, 0x41, 0x58, 0x2C, 0x31, 0x2C, 0x32, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x2D, 0x33, 0x30, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x2A, 0x53, 0x53, 0x5F, 0x52, 0x41, 0x57, + 0x5F, 0x4C, 0x4F, 0x57, 0x50, 0x4F, 0x57, 0x45, 0x52, 0x5F, 0x44, 0x41, + 0x54, 0x41, 0x5F, 0x46, 0x4F, 0x52, 0x43, 0x45, 0x5F, 0x4D, 0x49, 0x4E, + 0x5F, 0x4D, 0x41, 0x58, 0x2C, 0x31, 0x2C, 0x32, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x2D, 0x33, 0x30, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x2A, 0x53, 0x53, 0x5F, 0x54, 0x4F, 0x55, + 0x43, 0x48, 0x5F, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x5F, 0x54, 0x4F, + 0x54, 0x41, 0x4C, 0x5F, 0x49, 0x58, 0x5F, 0x46, 0x4F, 0x52, 0x43, 0x45, + 0x5F, 0x4D, 0x49, 0x4E, 0x2C, 0x31, 0x37, 0x2C, 0x31, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x32, 0x35, 0x30, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, + 0x32, 0x35, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x0D, 0x0A, 0x32, 0x35, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x32, 0x35, 0x30, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, + 0x32, 0x35, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x0D, 0x0A, 0x32, 0x35, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x32, 0x35, 0x30, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, + 0x32, 0x35, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x0D, 0x0A, 0x32, 0x35, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x32, 0x35, 0x30, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, + 0x32, 0x35, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x0D, 0x0A, 0x32, 0x35, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x32, 0x35, 0x30, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, + 0x32, 0x35, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x0D, 0x0A, 0x32, 0x35, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x32, 0x35, 0x30, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, + 0x32, 0x35, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x0D, 0x0A, 0x2A, 0x53, 0x53, 0x5F, 0x54, 0x4F, 0x55, 0x43, + 0x48, 0x5F, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x5F, 0x54, 0x4F, 0x54, + 0x41, 0x4C, 0x5F, 0x49, 0x58, 0x5F, 0x46, 0x4F, 0x52, 0x43, 0x45, 0x5F, + 0x4D, 0x41, 0x58, 0x2C, 0x31, 0x37, 0x2C, 0x31, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x34, 0x35, 0x30, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x34, + 0x35, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x0D, 0x0A, 0x34, 0x35, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x34, 0x35, 0x30, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x34, + 0x35, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x0D, 0x0A, 0x34, 0x35, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x34, 0x35, 0x30, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x34, + 0x35, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x0D, 0x0A, 0x34, 0x35, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x34, 0x35, 0x30, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x34, + 0x35, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x0D, 0x0A, 0x34, 0x35, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x34, 0x35, 0x30, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x34, + 0x35, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x0D, 0x0A, 0x34, 0x35, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x34, 0x35, 0x30, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x34, + 0x35, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x0D, 0x0A, 0x2A, 0x53, 0x53, 0x5F, 0x44, 0x45, 0x54, 0x45, 0x43, + 0x54, 0x5F, 0x54, 0x4F, 0x54, 0x41, 0x4C, 0x5F, 0x49, 0x58, 0x5F, 0x46, + 0x4F, 0x52, 0x43, 0x45, 0x5F, 0x4D, 0x49, 0x4E, 0x2C, 0x31, 0x37, 0x2C, + 0x31, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, + 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x0D, 0x0A, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x0D, 0x0A, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x30, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x30, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, + 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x0D, 0x0A, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x0D, 0x0A, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x30, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x30, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, + 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x0D, 0x0A, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x0D, 0x0A, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x30, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x2A, 0x53, + 0x53, 0x5F, 0x44, 0x45, 0x54, 0x45, 0x43, 0x54, 0x5F, 0x54, 0x4F, 0x54, + 0x41, 0x4C, 0x5F, 0x49, 0x58, 0x5F, 0x46, 0x4F, 0x52, 0x43, 0x45, 0x5F, + 0x4D, 0x41, 0x58, 0x2C, 0x31, 0x37, 0x2C, 0x31, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x35, 0x30, 0x30, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x35, + 0x30, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x0D, 0x0A, 0x35, 0x30, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x35, 0x30, 0x30, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x35, + 0x30, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x0D, 0x0A, 0x35, 0x30, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x35, 0x30, 0x30, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x35, + 0x30, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x0D, 0x0A, 0x35, 0x30, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x35, 0x30, 0x30, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x35, + 0x30, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x0D, 0x0A, 0x35, 0x30, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x35, 0x30, 0x30, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x35, + 0x30, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x0D, 0x0A, 0x35, 0x30, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x35, 0x30, 0x30, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x35, + 0x30, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x0D, 0x0A, 0x2A, 0x53, 0x53, 0x5F, 0x52, 0x41, 0x57, 0x5F, 0x44, + 0x41, 0x54, 0x41, 0x5F, 0x53, 0x45, 0x4E, 0x53, 0x45, 0x5F, 0x4D, 0x49, + 0x4E, 0x5F, 0x4D, 0x41, 0x58, 0x2C, 0x31, 0x2C, 0x32, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x2D, 0x33, 0x30, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x2A, 0x53, 0x53, 0x5F, 0x52, 0x41, + 0x57, 0x5F, 0x4C, 0x4F, 0x57, 0x50, 0x4F, 0x57, 0x45, 0x52, 0x5F, 0x44, + 0x41, 0x54, 0x41, 0x5F, 0x53, 0x45, 0x4E, 0x53, 0x45, 0x5F, 0x4D, 0x49, + 0x4E, 0x5F, 0x4D, 0x41, 0x58, 0x2C, 0x31, 0x2C, 0x32, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x2D, 0x33, 0x30, 0x30, + 0x30, 0x2C, 0x33, 0x30, 0x30, 0x30, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x2A, 0x53, 0x53, 0x5F, 0x54, 0x4F, + 0x55, 0x43, 0x48, 0x5F, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x5F, 0x54, + 0x4F, 0x54, 0x41, 0x4C, 0x5F, 0x49, 0x58, 0x5F, 0x53, 0x45, 0x4E, 0x53, + 0x45, 0x5F, 0x4D, 0x49, 0x4E, 0x2C, 0x31, 0x2C, 0x33, 0x36, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x31, 0x33, 0x30, + 0x2C, 0x31, 0x33, 0x30, 0x2C, 0x31, 0x33, 0x30, 0x2C, 0x31, 0x33, 0x30, + 0x2C, 0x31, 0x33, 0x30, 0x2C, 0x31, 0x33, 0x30, 0x2C, 0x31, 0x33, 0x30, + 0x2C, 0x31, 0x33, 0x30, 0x2C, 0x31, 0x33, 0x30, 0x2C, 0x31, 0x33, 0x30, + 0x2C, 0x31, 0x33, 0x30, 0x2C, 0x31, 0x33, 0x30, 0x2C, 0x31, 0x33, 0x30, + 0x2C, 0x31, 0x33, 0x30, 0x2C, 0x31, 0x33, 0x30, 0x2C, 0x31, 0x33, 0x30, + 0x2C, 0x31, 0x33, 0x30, 0x2C, 0x31, 0x33, 0x30, 0x2C, 0x31, 0x33, 0x30, + 0x2C, 0x31, 0x33, 0x30, 0x2C, 0x31, 0x33, 0x30, 0x2C, 0x31, 0x33, 0x30, + 0x2C, 0x31, 0x33, 0x30, 0x2C, 0x31, 0x33, 0x30, 0x2C, 0x31, 0x33, 0x30, + 0x2C, 0x31, 0x33, 0x30, 0x2C, 0x31, 0x33, 0x30, 0x2C, 0x31, 0x33, 0x30, + 0x2C, 0x31, 0x33, 0x30, 0x2C, 0x31, 0x33, 0x30, 0x2C, 0x31, 0x33, 0x30, + 0x2C, 0x31, 0x33, 0x30, 0x2C, 0x31, 0x33, 0x30, 0x2C, 0x31, 0x33, 0x30, + 0x2C, 0x31, 0x33, 0x30, 0x2C, 0x31, 0x33, 0x30, 0x0D, 0x0A, 0x2A, 0x53, + 0x53, 0x5F, 0x54, 0x4F, 0x55, 0x43, 0x48, 0x5F, 0x41, 0x43, 0x54, 0x49, + 0x56, 0x45, 0x5F, 0x54, 0x4F, 0x54, 0x41, 0x4C, 0x5F, 0x49, 0x58, 0x5F, + 0x53, 0x45, 0x4E, 0x53, 0x45, 0x5F, 0x4D, 0x41, 0x58, 0x2C, 0x31, 0x2C, + 0x33, 0x36, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, + 0x0A, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, 0x2C, 0x33, 0x30, 0x30, + 0x0D, 0x0A, 0x2A, 0x53, 0x53, 0x5F, 0x44, 0x45, 0x54, 0x45, 0x43, 0x54, + 0x5F, 0x54, 0x4F, 0x54, 0x41, 0x4C, 0x5F, 0x49, 0x58, 0x5F, 0x53, 0x45, + 0x4E, 0x53, 0x45, 0x5F, 0x4D, 0x49, 0x4E, 0x2C, 0x31, 0x2C, 0x33, 0x36, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x30, + 0x2C, 0x30, 0x2C, 0x30, 0x2C, 0x30, 0x2C, 0x30, 0x2C, 0x30, 0x2C, 0x30, + 0x2C, 0x30, 0x2C, 0x30, 0x2C, 0x30, 0x2C, 0x30, 0x2C, 0x30, 0x2C, 0x30, + 0x2C, 0x30, 0x2C, 0x30, 0x2C, 0x30, 0x2C, 0x30, 0x2C, 0x30, 0x2C, 0x30, + 0x2C, 0x30, 0x2C, 0x30, 0x2C, 0x30, 0x2C, 0x30, 0x2C, 0x30, 0x2C, 0x30, + 0x2C, 0x30, 0x2C, 0x30, 0x2C, 0x30, 0x2C, 0x30, 0x2C, 0x30, 0x2C, 0x30, + 0x2C, 0x30, 0x2C, 0x30, 0x2C, 0x30, 0x2C, 0x30, 0x2C, 0x30, 0x0D, 0x0A, + 0x2A, 0x53, 0x53, 0x5F, 0x44, 0x45, 0x54, 0x45, 0x43, 0x54, 0x5F, 0x54, + 0x4F, 0x54, 0x41, 0x4C, 0x5F, 0x49, 0x58, 0x5F, 0x53, 0x45, 0x4E, 0x53, + 0x45, 0x5F, 0x4D, 0x41, 0x58, 0x2C, 0x31, 0x2C, 0x33, 0x36, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x0D, 0x0A, 0x32, 0x35, 0x35, + 0x2C, 0x32, 0x35, 0x35, 0x2C, 0x32, 0x35, 0x35, 0x2C, 0x32, 0x35, 0x35, + 0x2C, 0x32, 0x35, 0x35, 0x2C, 0x32, 0x35, 0x35, 0x2C, 0x32, 0x35, 0x35, + 0x2C, 0x32, 0x35, 0x35, 0x2C, 0x32, 0x35, 0x35, 0x2C, 0x32, 0x35, 0x35, + 0x2C, 0x32, 0x35, 0x35, 0x2C, 0x32, 0x35, 0x35, 0x2C, 0x32, 0x35, 0x35, + 0x2C, 0x32, 0x35, 0x35, 0x2C, 0x32, 0x35, 0x35, 0x2C, 0x32, 0x35, 0x35, + 0x2C, 0x32, 0x35, 0x35, 0x2C, 0x32, 0x35, 0x35, 0x2C, 0x32, 0x35, 0x35, + 0x2C, 0x32, 0x35, 0x35, 0x2C, 0x32, 0x35, 0x35, 0x2C, 0x32, 0x35, 0x35, + 0x2C, 0x32, 0x35, 0x35, 0x2C, 0x32, 0x35, 0x35, 0x2C, 0x32, 0x35, 0x35, + 0x2C, 0x32, 0x35, 0x35, 0x2C, 0x32, 0x35, 0x35, 0x2C, 0x32, 0x35, 0x35, + 0x2C, 0x32, 0x35, 0x35, 0x2C, 0x32, 0x35, 0x35, 0x2C, 0x32, 0x35, 0x35, + 0x2C, 0x32, 0x35, 0x35, 0x2C, 0x32, 0x35, 0x35, 0x2C, 0x32, 0x35, 0x35, + 0x2C, 0x32, 0x35, 0x35, 0x2C, 0x32, 0x35, 0x35, 0x0D, 0x0A, +}; + +#endif diff --git a/fts_proc.c b/fts_proc.c new file mode 100644 index 0000000..cb030ca --- /dev/null +++ b/fts_proc.c @@ -0,0 +1,3744 @@ +/* + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com * + ************************************************************************** + * * + * Utilities published in /proc/fts * + * * + ************************************************************************** + ************************************************************************** + * + */ + +/*! + * \file fts_proc.c + * \brief contains the function and variables needed to publish a file node in + * the file system which allow to communicate with the IC from userspace + */ + +#include <linux/ctype.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/proc_fs.h> +#include <linux/fs.h> +#include <linux/seq_file.h> +#include <linux/delay.h> +#include <linux/uaccess.h> +#include "fts.h" +#include "fts_lib/ftsCompensation.h" +#include "fts_lib/ftsCore.h" +#include "fts_lib/ftsIO.h" +#include "fts_lib/ftsError.h" +#include "fts_lib/ftsFrame.h" +#include "fts_lib/ftsFlash.h" +#include "fts_lib/ftsTest.h" +#include "fts_lib/ftsTime.h" +#include "fts_lib/ftsTool.h" + + +#define DRIVER_TEST_FILE_NODE "driver_test" /* /< name of file node + * published */ +#define CHUNK_PROC 1024 /* /< Max chunk of data printed on + * the sequential file in each + * iteration */ +#define DIAGNOSTIC_NUM_FRAME 10 /* /< number of frames reading + * iterations during the diagnostic + * test */ + + + +/** @defgroup proc_file_code Proc File Node + * @ingroup file_nodes + * The /proc/fts/driver_test file node provide expose the most important API + * implemented into the driver to execute any possible operation into the IC \n + * Thanks to a series of Operation Codes, each of them, with a different set of + * parameter, it is possible to select a function to execute\n + * The result of the function is usually returned into the shell as an ASCII + * hex string where each byte is encoded in two chars.\n + * @{ + */ + +/* Bus operations */ +#define CMD_READ 0x00 /* /< I2C/SPI read: need + * to pass: byteToRead1 + * byteToRead0 + * (optional) dummyByte + * */ +#define CMD_WRITE 0x01 /* /< I2C/SPI write: + * need to pass: cmd[0] + * cmd[1] … + * cmd[cmdLength-1] */ +#define CMD_WRITEREAD 0x02 /* /< I2C/SPI writeRead: + * need to pass: cmd[0] + * cmd[1] … + * cmd[cmdLength-1] + * byteToRead1 + * byteToRead0 dummyByte + * */ +#define CMD_WRITETHENWRITEREAD 0x03 /* /< I2C/SPI write then + * writeRead: need to + * pass: cmdSize1 + * cmdSize2 cmd1[0] + * cmd1[1] … + * cmd1[cmdSize1-1] + * cmd2[0] cmd2[1] … + * cmd2[cmdSize2-1] + * byteToRead1 + * byteToRead0 */ +#define CMD_WRITEU8UX 0x04 + /* /< I2C/SPI + * writeU8UX: + * need to pass: cmd + * addrSize addr[0] … + * addr[addrSize-1] + * data[0] data[1] … */ +#define CMD_WRITEREADU8UX 0x05 /* /< I2C/SPI + * writeReadU8UX: need + * to pass: cmd addrSize + * addr[0] … + * addr[addrSize-1] + * byteToRead1 + * byteToRead0 + * hasDummyByte */ +#define CMD_WRITEU8UXTHENWRITEU8UX 0x06 + /* /< I2C/SPI writeU8UX + * then writeU8UX: need + * to pass: cmd1 + * addrSize1 cmd2 + * addrSize2 addr[0] … + * addr[addrSize1+ + * addrSize2-1] + * data[0] data[1] … */ +#define CMD_WRITEU8UXTHENWRITEREADU8UX 0x07 /* /< I2C/SPI writeU8UX + * then writeReadU8UX: + * need to pass: cmd1 + * addrSize1 cmd2 + * addrSize2 addr[0] … + * addr[addrSize1+ + * addrSize2-1] + * byteToRead1 + * byteToRead0 + * hasDummybyte */ +#define CMD_GETLIMITSFILE 0x08 /* /< Get the Production + * Limits File and print + * its content into the + * shell: need to pass: + * path(optional) + * otherwise select the + * approach chosen at + * compile time */ +#define CMD_GETFWFILE 0x09 /* /< Get the FW file + * and print its content + * into the shell: need + * to pass: path + * (optional) otherwise + * select the approach + * chosen at compile + * time */ +#define CMD_VERSION 0x0A /* /< Get the driver + * version and other + * driver setting info + * */ +#define CMD_READCONFIG 0x0B /* /< Read The config + * memory, need to pass: + * addr[0] addr[1] + * byteToRead0 + * byteToRead1 */ + + +/* GUI utils byte ver */ +#define CMD_READ_BYTE 0xF0 /* /< Byte output + * version of I2C/SPI + * read @see CMD_READ */ +#define CMD_WRITE_BYTE 0xF1 /* /< Byte output + * version of I2C/SPI + * write @see CMD_WRITE + * */ +#define CMD_WRITEREAD_BYTE 0xF2 /* /< Byte output + * version of I2C/SPI + * writeRead @see + * CMD_WRITEREAD */ +#define CMD_WRITETHENWRITEREAD_BYTE 0xF3 + /* /< Byte output + * version of I2C/SPI + * write then writeRead + * @see + * CMD_WRITETHENWRITEREAD + * */ +#define CMD_WRITEU8UX_BYTE 0xF4 /* /< Byte output + * version of I2C/SPI + * writeU8UX @see + * CMD_WRITEU8UX */ +#define CMD_WRITEREADU8UX_BYTE 0xF5 /* /< Byte output + * version of I2C/SPI + * writeReadU8UX @see + * CMD_WRITEREADU8UX */ +#define CMD_WRITEU8UXTHENWRITEU8UX_BYTE 0xF6 + /* /< Byte output + * version of I2C/SPI + * writeU8UX then + * writeU8UX @see + * CMD_WRITEU8UXTHENWRITEU8UX + * */ +#define CMD_WRITEU8UXTHENWRITEREADU8UX_BYTE 0xF7 + /* /< Byte output + * version of I2C/SPI + * writeU8UX then + * writeReadU8UX @see + * CMD_WRITEU8UXTHENWRITEREADU8UX + * */ +#define CMD_GETLIMITSFILE_BYTE 0xF8 /* /< Byte output + * version of Production + * Limits File @see + * CMD_GETLIMITSFILE */ +#define CMD_GETFWFILE_BYTE 0xF9 /* /< Byte output + * version of FW file + * need to pass: @see + * CMD_GETFWFILE */ +#define CMD_VERSION_BYTE 0xFA /* /< Byte output + * version of driver + * version and setting + * @see CMD_VERSION */ +#define CMD_CHANGE_OUTPUT_MODE 0xFF /* /< Select the output + * mode of the + * scriptless protocol, + * need to pass: + * bin_output = 1 data + * returned as binary, + * bin_output =0 data + * returned as hex + * string */ + +/* Core/Tools */ +#define CMD_POLLFOREVENT 0x11 /* /< Poll the FIFO for + * an event: need to + * pass: eventLength + * event[0] event[1] … + * event[eventLength-1] + * timeToWait1 + * timeToWait0 */ +#define CMD_SYSTEMRESET 0x12 /* /< System Reset */ +#define CMD_CLEANUP 0x13 /* /< Perform a system + * reset and optionally + * re-enable the + * scanning, need to + * pass: enableTouch */ +#define CMD_POWERCYCLE 0x14 /* /< Execute a power + * cycle toggling the + * regulators */ +#define CMD_READSYSINFO 0x15 /* /< Read the System + * Info information from + * the framebuffer, need + * to pass: doRequest */ +#define CMD_FWWRITE 0x16 + /* /< Write a FW + * command: need to + * pass: cmd[0] cmd[1] + * … cmd[cmdLength-1] */ +#define CMD_INTERRUPT 0x17 /* /< Allow to enable or + * disable the + * interrupts, need to + * pass: enable (if 1 + * will enable the + * interrupt) */ +#define CMD_SETSCANMODE 0x18 /* /< set Scan Mode + * need to pass: + * scanType option + */ +#define CMD_SAVEMPFLAG 0x19 /* /< save manually a + * value in the MP flag + * need to pass: mpflag + */ + +/* Frame */ +#define CMD_GETFORCELEN 0x20 /* /< Get the number of + * Force channels */ +#define CMD_GETSENSELEN 0x21 /* /< Get the number of + * Sense channels */ +#define CMD_GETMSFRAME 0x23 /* /< Get a MS frame: + * need to pass: + * MSFrameType */ +#define CMD_GETSSFRAME 0x24 /* /< Get a SS frame: + * need to pass: + * SSFrameType */ +#define CMD_GETSYNCFRAME 0x25 /* /< Get a SS frame: + * need to pass: + * SSFrameType + */ + +/* Compensation */ +#define CMD_REQCOMPDATA 0x30 /* /< Request Init data: + * need to pass: type */ +#define CMD_READCOMPDATAHEAD 0x31 /* /< Read Init data + * header: need to pass: + * type */ +#define CMD_READMSCOMPDATA 0x32 /* /< Read MS Init data: + * need to pass: type */ +#define CMD_READSSCOMPDATA 0x33 /* /< Read SS Init data: + * need to pass: type */ +#define CMD_READGOLDENMUTUAL 0x34 /* /< Read GoldenMutual + raw data */ +#define CMD_READTOTMSCOMPDATA 0x35 /* /< Read Tot MS Init + * data: need to pass: + * type */ +#define CMD_READTOTSSCOMPDATA 0x36 /* /< Read Tot SS Init + * data: need to pass: + * type */ +#define CMD_READSENSCOEFF 0x37 /* /< Read MS and SS + * Sensitivity + * Calibration + * Coefficients */ + +/* FW Update */ +#define CMD_GETFWVER 0x40 /* /< Get the FW version + * of the IC */ +#define CMD_FLASHUNLOCK 0x42 /* /< Unlock the flash + * */ +#define CMD_READFWFILE 0x43 /* /< Try to read the FW + * file, need to pass: + * keep_cx */ +#define CMD_FLASHPROCEDURE 0x44 /* /< Perform a full + * flashing procedure: + * need to pass: force + * keep_cx */ +#define CMD_FLASHERASEUNLOCK 0x45 /* /< Unlock the erase + * of the flash */ +#define CMD_FLASHERASEPAGE 0x46 + /* /< Erase page by page + * the flash, need to + * pass: keep_cx, if + * keep_cx>SKIP_PANEL_INIT + * Panel Init Page will + * be skipped, if + * >SKIP_PANEL_CX_INIT + * Cx and Panel Init + * Pages will be skipped + * otherwise if + * =ERASE_ALL all the + * pages will be deleted + * */ + +/* MP test */ +#define CMD_ITOTEST 0x50 /* /< Perform an ITO + * test */ +#define CMD_INITTEST 0x51 /* /< Perform an + * Initialization test: + * need to pass: type */ +#define CMD_MSRAWTEST 0x52 /* /< Perform MS raw + * test: need to pass + * stop_on_fail */ +#define CMD_MSINITDATATEST 0x53 /* /< Perform MS Init + * data test: need to + * pass stop_on_fail */ +#define CMD_SSRAWTEST 0x54 /* /< Perform SS raw + * test: need to pass + * stop_on_fail */ +#define CMD_SSINITDATATEST 0x55 /* /< Perform SS Init + * data test: need to + * pass stop_on_fail */ +#define CMD_MAINTEST 0x56 /* /< Perform a full + * Mass production test: + * need to pass + * stop_on_fail saveInit + * */ +#define CMD_FREELIMIT 0x57 /* /< Free (if any) + * limit file which was + * loaded during any + * test procedure */ + +/* Diagnostic */ +#define CMD_DIAGNOSTIC 0x60 /* /< Perform a series + * of commands and + * collect severals data + * to detect any + * malfunction */ + +#define CMD_CHANGE_SAD 0x70 /* /< Allow to change + * the SAD address (for + * debugging) */ +#define CMD_INFOBLOCK_STATUS 0x61 /* /< Check for Info + * block error */ + +/* Debug functionalities requested by Google for B1 Project */ +#define CMD_TRIGGER_FORCECAL 0x80 /* /< Trigger manually + * forcecal for MS and + * SS */ +#define CMD_BASELINE_ADAPTATION 0x81 /* /< Enable/Disable + * Baseline adaptation, + * need to pass: enable + * */ +#define CMD_FREQ_HOP 0x82 /* /< Enable/Disable + * Frequency hopping, + * need to pass: enable + * */ +#define CMD_SET_OPERATING_FREQ 0x83 /* /< Set a defined + * scanning frequency in + * Hz passed as 4 bytes + * in big endian, need + * to pass: freq3 freq2 + * freq1 freq0 */ +#define CMD_READ_SYNC_FRAME 0x84 + /* /< Read Sync Frame + * which contain MS and + * SS data, need to + * pass: frameType (this + * parameter can be + * LOAD_SYNC_FRAME_STRENGTH + * or LOAD_SYNC_FRAME_BASELINE) + * */ + + +#define CMD_TP_SENS_MODE 0x90 /* /< Enter/Exit in the + * TP Sensitivity + * Calibration mode, + * need to pass: enter + * (optional)saveGain */ +#define CMD_TP_SENS_SET_SCAN_MODE 0x91 /* /< Set scan mode type + * which should be used + * for the test before + * the stimpad is down, + * need to pass: type */ +#define CMD_TP_SENS_PRECAL_SS 0x92 /* /< Perform Pre + * Calibration for SS + * steps when stimpad is + * down */ +#define CMD_TP_SENS_PRECAL_MS 0x93 /* /< Perform Pre + * Calibration for MS + * steps when stimpad is + * down */ +#define CMD_TP_SENS_POSTCAL_MS 0x94 /* /< Perform Post + * Calibration for MS + * steps when stimpad is + * down */ +#define CMD_TP_SENS_STD 0x95 /* /< Compute the + * Standard deviation of + * a certain number of + * frames, need to pass: + * numFrames */ + +#define CMD_FORCE_TOUCH_ACTIVE 0xA0 /* /< Prevent the driver + * from transitioning + * the ownership of the + * bus to SLPI + */ + +static u8 bin_output; /* /< Select the output type of the scriptless + * protocol (binary = 1 or hex string = 0) */ +/** @}*/ + +/** @defgroup scriptless Scriptless Protocol + * @ingroup proc_file_code + * Scriptless Protocol allows ST Software (such as FingerTip Studio etc) to + * communicate with the IC from an user space. + * This mode gives access to common bus operations (write, read etc) and + * support additional functionalities. \n + * The protocol is based on exchange of binary messages included between a + * start and an end byte + * @{ + */ + +#define MESSAGE_START_BYTE 0x7B /* /< start byte of each message + * transferred in Scriptless Mode */ +#define MESSAGE_END_BYTE 0x7D /* /< end byte of each message + * transferred in Scriptless Mode */ +#define MESSAGE_MIN_HEADER_SIZE 8 /* /< minimun number of bytes of the + * structure of a messages exchanged + * with host (include start/end byte, + * counter, actions, msg_size) */ + +/** + * Possible actions that can be requested by an host + */ +typedef enum { + ACTION_WRITE = (u16) 0x0001, /* /< Bus Write + * */ + ACTION_READ = (u16) 0x0002, /* /< Bus Read + * */ + ACTION_WRITE_READ = (u16) 0x0003, /* /< Bus Write + * followed by a + * Read */ + ACTION_GET_VERSION = (u16) 0x0004, /* /< Get + * Version of + * the protocol + * (equal to the + * first 2 bye + * of driver + * version) */ + ACTION_WRITEU8UX = (u16) 0x0011, /* /< Bus Write + * with support + * to different + * address size + * */ + ACTION_WRITEREADU8UX = (u16) 0x0012, /* /< Bus + * writeRead + * with support + * to different + * address size + * */ + ACTION_WRITETHENWRITEREAD = (u16) 0x0013, /* /< Bus write + * followed by a + * writeRead */ + ACTION_WRITEU8XTHENWRITEREADU8UX = (u16) 0x0014, /* /< Bus write + * followed by a + * writeRead + * with support + * to different + * address size + * */ + ACTION_WRITEU8UXTHENWRITEU8UX = (u16) 0x0015, /* /< Bus write + * followed by a + * write with + * support to + * different + * address size + * */ + ACTION_GET_FW = (u16) 0x1000, /* /< Get Fw + * file content + * used by the + * driver */ + ACTION_GET_LIMIT = (u16) 0x1001 /* /< Get Limit + * File content + * used by the + * driver */ +} Actions; + +/** + * Struct used to contain info of the message received by the host in + * Scriptless mode + */ +typedef struct { + u16 msg_size; /* /< total size of the message in bytes */ + u16 counter; /* /< counter ID to identify a message */ + Actions action; /* /< type of operation requested by the host @see + * Actions */ + u8 dummy; /* /< (optional)in case of any kind of read operations, + * specify if the first byte is dummy */ +} Message; + +/** @}*/ + + + +extern TestToDo tests; +extern SysInfo systemInfo; + +static int limit; /* /< store the amount of data to print into the + * shell */ +static int chunk; /* /< store the chuk of data that should be printed in + * this iteration */ +static int printed; /* /< store the amount of data already printed in the + * shell */ +static struct proc_dir_entry *fts_dir; /* /< reference to the directory + * fts under /proc */ +static u8 *driver_test_buff; /* /< pointer to an array of bytes used + * to store the result of the function + * executed */ +char buf_chunk[CHUNK_PROC] = { 0 }; /* /< buffer used to store the message + * info received */ +static Message mess = { 0 }; /* /< store the information of the Scriptless + * message received */ + + +/************************ SEQUENTIAL FILE UTILITIES **************************/ +/** + * This function is called at the beginning of the stream to a sequential file + * or every time into the sequential were already written PAGE_SIZE bytes and + * the stream need to restart + * @param s pointer to the sequential file on which print the data + * @param pos pointer to the offset where write the data + * @return NULL if there is no data to print or the pointer to the beginning of + * the data that need to be printed + */ +static void *fts_seq_start(struct seq_file *s, loff_t *pos) +{ + pr_info("%s: Entering start(), pos = %lld limit = %d printed = %d\n", + __func__, *pos, limit, printed); + + if (driver_test_buff == NULL && *pos == 0) { + int size = 13 * sizeof(u8); + + pr_info("%s: No data to print!\n", __func__); + driver_test_buff = (u8 *)kmalloc(size, GFP_KERNEL); + limit = scnprintf(driver_test_buff, + size, + "{ %08X }\n", ERROR_OP_NOT_ALLOW); + /* pr_err("%s: len = %d driver_test_buff = %s\n", + * __func__, limit, driver_test_buff); */ + } else { + if (*pos != 0) + *pos += chunk - 1; + + if (*pos >= limit) + /* pr_err("%s: Apparently, we're done.\n", __func__); */ + return NULL; + } + + chunk = CHUNK_PROC; + if (limit - *pos < CHUNK_PROC) + chunk = limit - *pos; + /* pr_err("%s: In start(), + * updated pos = %Ld limit = %d printed = %d chunk = %d\n", + * __func__, *pos, limit, printed, chunk); */ + memset(buf_chunk, 0, CHUNK_PROC); + memcpy(buf_chunk, &driver_test_buff[(int)*pos], chunk); + + return buf_chunk; +} + +/** + * This function actually print a chunk amount of data in the sequential file + * @param s pointer to the sequential file where to print the data + * @param v pointer to the data to print + * @return 0 + */ +static int fts_seq_show(struct seq_file *s, void *v) +{ + /* pr_err("%s: In show()\n", __func__); */ + seq_write(s, (u8 *)v, chunk); + printed += chunk; + return 0; +} + +/** + * This function update the pointer and the counters to the next data to be + * printed + * @param s pointer to the sequential file where to print the data + * @param v pointer to the data to print + * @param pos pointer to the offset where write the next data + * @return NULL if there is no data to print or the pointer to the beginning of + * the next data that need to be printed + */ +static void *fts_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + /* int* val_ptr; */ + /* pr_err("%s: In next(), v = %X, pos = %Ld.\n", __func__, + * v, *pos); */ + (*pos) += chunk;/* increase my position counter */ + chunk = CHUNK_PROC; + + /* pr_err("%s: In next(), + * updated pos = %Ld limit = %d printed = %d\n", + * __func__, *pos, limit,printed); */ + if (*pos >= limit) /* are we done? */ + return NULL; + else if (limit - *pos < CHUNK_PROC) + chunk = limit - *pos; + + + memset(buf_chunk, 0, CHUNK_PROC); + memcpy(buf_chunk, &driver_test_buff[(int)*pos], chunk); + return buf_chunk; +} + + +/** + * This function is called when there are no more data to print the stream + *need to be terminated or when PAGE_SIZE data were already written into the + *sequential file + * @param s pointer to the sequential file where to print the data + * @param v pointer returned by fts_seq_next + */ +static void fts_seq_stop(struct seq_file *s, void *v) +{ + /* pr_err("%s: Entering stop().\n", __func__); */ + + if (v) { + /* pr_err("%s: v is %X.\n", __func__, v); */ + } else { + /* pr_err("%s: v is null.\n", __func__); */ + limit = 0; + chunk = 0; + printed = 0; + if (driver_test_buff != NULL) { + /* pr_err("%s: Freeing and clearing driver_test_buff.\n", + * __func__); */ + kfree(driver_test_buff); + driver_test_buff = NULL; + } else { + /* pr_err("%s: driver_test_buff is already null.\n", + * __func__); */ + } + } +} + +/** + * Struct where define and specify the functions which implements the flow for + * writing on a sequential file + */ +static const struct seq_operations fts_seq_ops = { + .start = fts_seq_start, + .next = fts_seq_next, + .stop = fts_seq_stop, + .show = fts_seq_show +}; + +/** + * This function open a sequential file + * @param inode Inode in the file system that was called and triggered this + * function + * @param file file associated to the file node + * @return error code, 0 if success + */ +static int fts_driver_test_open(struct inode *inode, struct file *file) +{ + struct fts_ts_info *info = dev_get_drvdata(getDev()); + int retval; + + if (!info) { + pr_err("%s: Unable to access driver data\n", __func__); + retval = -ENODEV; + goto exit; + } + + if (!mutex_trylock(&info->diag_cmd_lock)) { + pr_err("%s: Blocking concurrent access\n", __func__); + retval = -EBUSY; + goto exit; + } + + /* Allowing only a single process to open diag procfs node */ + if (info->diag_node_open == true) { + pr_err("%s: Blocking multiple open\n", __func__); + retval = -EBUSY; + goto unlock; + } + + retval = seq_open(file, &fts_seq_ops); + if(!retval) { + info->diag_node_open = true; + } + +unlock: + mutex_unlock(&info->diag_cmd_lock); +exit: + return retval; +}; + +/** + * This function closes a sequential file + * @param inode Inode in the file system that was called and triggered this + * function + * @param file file associated to the file node + * @return error code, 0 if success + */ +static int fts_driver_test_release(struct inode *inode, struct file *file) +{ + struct fts_ts_info *info = dev_get_drvdata(getDev()); + int retval; + + if (info) + mutex_lock(&info->diag_cmd_lock); + else + pr_err("%s: Unable to access driver data\n", __func__); + + retval = seq_release(inode, file); + + if (info) { + info->diag_node_open = false; + mutex_unlock(&info->diag_cmd_lock); + } + + return retval; +} + +/*****************************************************************************/ + +/**************************** DRIVER TEST ************************************/ + +/** @addtogroup proc_file_code + * @{ + */ + +/** + * Receive the OP code and the inputs from shell when the file node is called, + * parse it and then execute the corresponding function + * echo cmd+parameters > /proc/fts/driver_test to execute the select command + * cat /proc/fts/driver_test to obtain the result into the + * shell \n + * the string returned in the shell is made up as follow: \n + * { = start byte \n + * the answer content and format strictly depend on the cmd executed. In + * general can be: an HEX string or a byte array (e.g in case of 0xF- commands) + * \n + * } = end byte \n + */ +static ssize_t fts_driver_test_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + int numberParam = 0; + struct fts_ts_info *info = dev_get_drvdata(getDev()); + char *p = NULL; + char *pbuf = NULL; + char path[100] = { 0 }; + int res = -1, j, index = 0; + u8 report = 0; + int size = 6; + int temp, byte_call = 0; + u16 byteToRead = 0; + u32 fileSize = 0; + u8 *readData = NULL; + u8 *cmd = NULL; /* worst case needs count bytes */ + u32 maxNum_cmd = 0; + u32 *funcToTest = NULL; + u32 maxNum_funcToTest = 0; + u64 addr = 0; + MutualSenseFrame frameMS; + MutualSenseFrame deltas; + SelfSenseFrame frameSS; + u8 error_to_search[2] = { EVT_TYPE_ERROR_OSC_TRIM, + EVT_TYPE_ERROR_AOFFSET_TRIM }; + + DataHeader dataHead; + MutualSenseData compData; + SelfSenseData comData; + TotMutualSenseData totCompData; + TotSelfSenseData totComData; + MutualSenseCoeff msCoeff; + SelfSenseCoeff ssCoeff; + GoldenMutualRawData gmRawData; + int meanNorm = 0, meanEdge = 0; + + u64 address; + + Firmware fw; + LimitFile lim; + const char *limits_file = info->board->limits_name; + + if (!info) { + pr_err("%s: Unable to access driver data\n", __func__); + count = -ENODEV; + goto exit; + } + + mess.dummy = 0; + mess.action = 0; + mess.msg_size = 0; + + if (fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, true) < 0) { + res = ERROR_BUS_WR; + pr_err("%s: bus is not accessible.\n", __func__); + if (driver_test_buff) + limit = scnprintf(driver_test_buff, size, "{ %08X }\n", + res); + fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, false); + goto ERROR; + } + + if (count < 2) { + res = ERROR_OP_NOT_ALLOW; + goto ERROR; + } + + /* alloc one more space to store '\0' at the end + * for "echo -n CMDs" case + */ + pbuf = kmalloc((count + 1) * sizeof(*pbuf), GFP_KERNEL); + if (!pbuf) { + res = ERROR_ALLOC; + goto ERROR; + } + pbuf[count] = '\0'; + + + maxNum_funcToTest = (count + 1) / 3; + funcToTest = kmalloc_array(maxNum_funcToTest, sizeof(*funcToTest), + GFP_KERNEL); + if (!funcToTest) { + res = ERROR_ALLOC; + goto ERROR; + } + + /*for(temp = 0; temp<count; temp++){ + * pr_err("p[%d] = %02X\n", temp, p[temp]); + * }*/ + if (access_ok(VERIFY_READ, buf, count) < OK || + copy_from_user(pbuf, buf, count) != 0) { + res = ERROR_ALLOC; + goto END; + } + + maxNum_cmd = count; + cmd = (u8 *)kmalloc_array(maxNum_cmd, sizeof(u8), GFP_KERNEL); + if (cmd == NULL) { + res = ERROR_ALLOC; + pr_err("%s: Impossible allocate memory... ERROR %08X!\n", + __func__, res); + goto ERROR; + } + + p = pbuf; + if (count > MESSAGE_MIN_HEADER_SIZE - 1 && p[0] == MESSAGE_START_BYTE) { + pr_info("Enter in Byte Mode!\n"); + byte_call = 1; + mess.msg_size = (p[1] << 8) | p[2]; + mess.counter = (p[3] << 8) | p[4]; + mess.action = (p[5] << 8) | p[6]; + pr_info("Message received: size = %d, counter_id = %d, action = %04X\n", + mess.msg_size, mess.counter, mess.action); + size = MESSAGE_MIN_HEADER_SIZE + 2; /* +2 error code */ + if (count < mess.msg_size || p[count - 2] != MESSAGE_END_BYTE) { + pr_err("number of byte received or end byte wrong! msg_size = %d != %zu, last_byte = %02X != %02X ... ERROR %08X\n", + mess.msg_size, count, p[count - 1], + MESSAGE_END_BYTE, ERROR_OP_NOT_ALLOW); + res = ERROR_OP_NOT_ALLOW; + goto END; + } else { + numberParam = mess.msg_size - MESSAGE_MIN_HEADER_SIZE + + 1; /* +1 because put the internal + * op code */ + size = MESSAGE_MIN_HEADER_SIZE + 2; /* +2 send also + * the first 2 + * lsb of the + * error code */ + switch (mess.action) { + case ACTION_READ: + /* numberParam = + * mess.msg_size-MESSAGE_MIN_HEADER_SIZE+1; */ + cmd[0] = funcToTest[0] = CMD_READ_BYTE; + break; + + case ACTION_WRITE: + cmd[0] = funcToTest[0] = CMD_WRITE_BYTE; + break; + + case ACTION_WRITE_READ: + cmd[0] = funcToTest[0] = CMD_WRITEREAD_BYTE; + break; + + case ACTION_GET_VERSION: + cmd[0] = funcToTest[0] = CMD_VERSION_BYTE; + break; + + case ACTION_WRITETHENWRITEREAD: + cmd[0] = funcToTest[0] = + CMD_WRITETHENWRITEREAD_BYTE; + break; + + case ACTION_WRITEU8UX: + cmd[0] = funcToTest[0] = CMD_WRITEU8UX_BYTE; + break; + + case ACTION_WRITEREADU8UX: + cmd[0] = funcToTest[0] = CMD_WRITEREADU8UX_BYTE; + break; + + case ACTION_WRITEU8UXTHENWRITEU8UX: + cmd[0] = funcToTest[0] = + CMD_WRITEU8UXTHENWRITEU8UX_BYTE; + break; + + case ACTION_WRITEU8XTHENWRITEREADU8UX: + cmd[0] = funcToTest[0] = + CMD_WRITEU8UXTHENWRITEREADU8UX_BYTE; + break; + + case ACTION_GET_FW: + cmd[0] = funcToTest[0] = CMD_GETFWFILE_BYTE; + break; + + case ACTION_GET_LIMIT: + cmd[0] = funcToTest[0] = CMD_GETLIMITSFILE_BYTE; + break; + + default: + pr_err("Invalid Action = %d ... ERROR %08X\n", + mess.action, ERROR_OP_NOT_ALLOW); + res = ERROR_OP_NOT_ALLOW; + goto END; + } + + if (numberParam - 1 != 0) + memcpy(&cmd[1], &p[7], numberParam - 1); + /* -1 because i need to exclude the cmd[0] */ + } + } else { + u8 result; + char *token; + char *path_token = NULL; + size_t token_len; + + /* newline case at last char */ + if (p[count - 1] == '\n') + p[count - 1] = '\0'; + + /* Parse the input string to retrieve 2 hex-digit width + * cmds/args separated by one or more spaces, except for + * the fw/limits file name. + */ + while (p && + numberParam < min(maxNum_cmd, maxNum_funcToTest)) { + + while (isspace(*p)) + p++; + + token = strsep(&p, " "); + + if (!token || *token == '\0') + break; + + token_len = strlen(token); + + /* break the loop to handle FW/LIMITS path */ + if (numberParam == 1 && + (funcToTest[0] == CMD_GETFWFILE || + funcToTest[0] == CMD_GETLIMITSFILE)) { + path_token = token; + break; + } + + if (token_len != 2) { + pr_err("bad len. len=%zu\n", token_len); + res = ERROR_OP_NOT_ALLOW; + goto ERROR; + } + + if (kstrtou8(token, 16, &result)) { + /* Conversion failed due to bad input. + * Discard the entire buffer. + */ + pr_err("bad input\n"); + res = ERROR_OP_NOT_ALLOW; + goto ERROR; + } + + /* found a valid cmd/args */ + cmd[numberParam] = funcToTest[numberParam] = result; + pr_info("functionToTest[%d] = %02X cmd[%d] = %02X\n", + numberParam, funcToTest[numberParam], + numberParam, cmd[numberParam]); + numberParam++; + } + + /* FW/LIMITS path */ + if (path_token && strlen(path_token)) { + strlcpy(path, path_token, sizeof(path)); + numberParam++; + } + + if (numberParam == 0) { + pr_err("Found invalid cmd/arg\n"); + res = ERROR_OP_NOT_ALLOW; + goto END; + } + } + + fw.data = NULL; + lim.data = NULL; + + pr_info("Number of Parameters = %d\n", numberParam); + + /* elaborate input */ + if (numberParam >= 1) { + switch (funcToTest[0]) { + case CMD_VERSION_BYTE: + pr_info("%s: Get Version Byte\n", __func__); + byteToRead = 2; + mess.dummy = 0; + readData = (u8 *)kmalloc(byteToRead * sizeof(u8), + GFP_KERNEL); + size += byteToRead; + if (readData != NULL) { + readData[0] = (u8)(FTS_TS_DRV_VER >> 24); + readData[1] = (u8)(FTS_TS_DRV_VER >> 16); + pr_info("%s: Version = %02X%02X\n", + __func__, readData[0], readData[1]); + res = OK; + } else { + res = ERROR_ALLOC; + pr_err("%s: Impossible allocate memory... ERROR %08X\n", + __func__, res); + } + break; + + + case CMD_VERSION: + byteToRead = 2 * sizeof(u32); + mess.dummy = 0; + readData = (u8 *)kmalloc(byteToRead * sizeof(u8), + GFP_KERNEL); + u32ToU8_be(FTS_TS_DRV_VER, readData); + fileSize = 0; + /* first two bytes bitmask of features enabled in the + * IC, second two bytes bitmask of features enabled in + * the driver */ + +#ifdef FW_H_FILE + fileSize |= 0x00010000; +#endif + +#ifdef LIMITS_H_FILE + fileSize |= 0x00020000; +#endif + +#ifdef USE_ONE_FILE_NODE + fileSize |= 0x00040000; +#endif + +#ifdef FW_UPDATE_ON_PROBE + fileSize |= 0x00080000; +#endif + +#ifdef PRE_SAVED_METHOD + fileSize |= 0x00100000; +#endif + +#ifdef COMPUTE_INIT_METHOD + fileSize |= 0x00200000; +#endif + +#ifdef USE_GESTURE_MASK + fileSize |= 0x00100000; +#endif + +#ifdef I2C_INTERFACE + fileSize |= 0x00200000; +#else + if (getClient() && (getClient()->mode & SPI_3WIRE) == 0) + fileSize |= 0x00400000; +#endif + +#ifdef PHONE_KEY /* it is a feature enabled in the config of the chip */ + fileSize |= 0x00000100; +#endif + +#ifdef GESTURE_MODE + fromIDtoMask(FEAT_SEL_GESTURE, (u8 *)&fileSize, 4); +#endif + + +#ifdef GRIP_MODE + fromIDtoMask(FEAT_SEL_GRIP, (u8 *)&fileSize, 4); +#endif + +#ifdef CHARGER_MODE + fromIDtoMask(FEAT_SEL_CHARGER, (u8 *)&fileSize, 4); +#endif + +#ifdef GLOVE_MODE + fromIDtoMask(FEAT_SEL_GLOVE, (u8 *)&fileSize, 4); +#endif + + +#ifdef COVER_MODE + fromIDtoMask(FEAT_SEL_COVER, (u8 *)&fileSize, 4); +#endif + +#ifdef STYLUS_MODE + fromIDtoMask(FEAT_SEL_STYLUS, (u8 *)&fileSize, 4); +#endif + + u32ToU8_be(fileSize, &readData[4]); + res = OK; + size += (byteToRead * sizeof(u8)); + break; + + case CMD_WRITEREAD: + case CMD_WRITEREAD_BYTE: + if (numberParam >= 5) { /* need to pass: cmd[0] cmd[1] + * … cmd[cmdLength-1] + * byteToRead1 byteToRead0 + * dummyByte */ + temp = numberParam - 4; + if (cmd[numberParam - 1] == 0) + mess.dummy = 0; + else + mess.dummy = 1; + + u8ToU16_be(&cmd[numberParam - 3], &byteToRead); + pr_info("bytesToRead = %d\n", + byteToRead + mess.dummy); + + readData = (u8 *)kmalloc((byteToRead + + mess.dummy) * + sizeof(u8), + GFP_KERNEL); + res = fts_writeRead_heap(&cmd[1], temp, + readData, byteToRead + mess.dummy); + size += (byteToRead * sizeof(u8)); + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_WRITE: + case CMD_WRITE_BYTE: + if (numberParam >= 2) { /* need to pass: cmd[0] cmd[1] + * … cmd[cmdLength-1] */ + temp = numberParam - 1; + + res = fts_write_heap(&cmd[1], temp); + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_READ: + case CMD_READ_BYTE: + if (numberParam >= 3) { /* need to pass: byteToRead1 + * byteToRead0 (optional) + * dummyByte */ + if (numberParam == 3 || + (numberParam == 4 && + cmd[numberParam - 1] == 0)) + mess.dummy = 0; + else + mess.dummy = 1; + u8ToU16_be(&cmd[1], &byteToRead); + readData = (u8 *)kmalloc((byteToRead + + mess.dummy) * + sizeof(u8), + GFP_KERNEL); + res = fts_read_heap(readData, byteToRead + + mess.dummy); + size += (byteToRead * sizeof(u8)); + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_WRITETHENWRITEREAD: + case CMD_WRITETHENWRITEREAD_BYTE: + /* need to pass: cmdSize1 cmdSize2 cmd1[0] cmd1[1] … + * cmd1[cmdSize1-1] cmd2[0] cmd2[1] … cmd2[cmdSize2-1] + * byteToRead1 byteToRead0 */ + if (numberParam >= 6) { + u8ToU16_be(&cmd[numberParam - 2], &byteToRead); + readData = (u8 *)kmalloc(byteToRead * + sizeof(u8), + GFP_KERNEL); + res = fts_writeThenWriteRead_heap( + &cmd[3], cmd[1], + &cmd[3 + (int)cmd[1]], cmd[2], + readData, byteToRead); + size += (byteToRead * sizeof(u8)); + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_WRITEU8UX: + case CMD_WRITEU8UX_BYTE: + /* need to pass: + * cmd addrSize addr[0] … addr[addrSize-1] + * data[0] data[1] … */ + if (numberParam >= 4) { + if (cmd[2] <= sizeof(u64)) { + u8ToU64_be(&cmd[3], &addr, cmd[2]); + pr_info("addr = %llx\n", addr); + res = fts_writeU8UX(cmd[1], cmd[2], + addr, + &cmd[3 + cmd[2]], + (numberParam - cmd[2] - 3)); + } else { + pr_err("Wrong address size!\n"); + res = ERROR_OP_NOT_ALLOW; + } + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + + case CMD_WRITEREADU8UX: + case CMD_WRITEREADU8UX_BYTE: + /* need to pass: + * cmd addrSize addr[0] … addr[addrSize-1] + * byteToRead1 byteToRead0 hasDummyByte */ + if (numberParam >= 6) { + if (cmd[2] <= sizeof(u64)) { + u8ToU64_be(&cmd[3], &addr, cmd[2]); + u8ToU16_be(&cmd[numberParam - 3], + &byteToRead); + readData = (u8 *)kmalloc(byteToRead * + sizeof(u8), + GFP_KERNEL); + pr_info("addr = %llx byteToRead = %d\n", + addr, byteToRead); + res = fts_writeReadU8UX(cmd[1], cmd[2], + addr, readData, + byteToRead, + cmd[numberParam - 1]); + size += (byteToRead * sizeof(u8)); + } else { + pr_err("Wrong address size!\n"); + res = ERROR_OP_NOT_ALLOW; + } + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_WRITEU8UXTHENWRITEU8UX: + case CMD_WRITEU8UXTHENWRITEU8UX_BYTE: + /* need to pass: + * cmd1 addrSize1 cmd2 addrSize2 addr[0] … + * addr[addrSize1+addrSize2-1] data[0] data[1] … */ + if (numberParam >= 6) { + if ((cmd[2] + cmd[4]) <= sizeof(u64)) { + u8ToU64_be(&cmd[5], &addr, cmd[2] + + cmd[4]); + + pr_info("addr = %llx\n", addr); + res = fts_writeU8UXthenWriteU8UX(cmd[1], + cmd[2], cmd[3], + cmd[4], addr, + &cmd[5 + cmd[2] + cmd[4]], + (numberParam - cmd[2] + - cmd[4] - 5)); + } else { + pr_err("Wrong address size!\n"); + res = ERROR_OP_NOT_ALLOW; + } + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_WRITEU8UXTHENWRITEREADU8UX: + case CMD_WRITEU8UXTHENWRITEREADU8UX_BYTE: + /* need to pass: + * cmd1 addrSize1 cmd2 addrSize2 addr[0] … + * addr[addrSize1+addrSize2-1] byteToRead1 byteToRead0 + * hasDummybyte */ + if (numberParam >= 8) { + if ((cmd[2] + cmd[4]) <= sizeof(u64)) { + u8ToU64_be(&cmd[5], &addr, cmd[2] + + cmd[4]); + pr_info("%s: cmd[5] = %02X, addr = %llx\n", + __func__, cmd[5], addr); + u8ToU16_be(&cmd[numberParam - 3], + &byteToRead); + readData = (u8 *)kmalloc(byteToRead * + sizeof(u8), + GFP_KERNEL); + res = fts_writeU8UXthenWriteReadU8UX( + cmd[1], cmd[2], cmd[3], cmd[4], + addr, + readData, byteToRead, + cmd[numberParam - 1]); + size += (byteToRead * sizeof(u8)); + } else { + pr_err("Wrong total address size!\n"); + res = ERROR_OP_NOT_ALLOW; + } + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + + break; + + case CMD_CHANGE_OUTPUT_MODE: + /* need to pass: bin_output */ + if (numberParam >= 2) { + bin_output = cmd[1]; + pr_info("Setting Scriptless output mode: %d\n", + bin_output); + res = OK; + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_FWWRITE: + if (numberParam >= 3) { /* need to pass: cmd[0] cmd[1] + * … cmd[cmdLength-1] */ + if (numberParam >= 2) { + temp = numberParam - 1; + res = fts_writeFwCmd_heap(&cmd[1], + temp); + } else { + pr_err("Wrong parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_INTERRUPT: + /* need to pass: enable */ + if (numberParam >= 2) { + if (cmd[1] == 1) + res = fts_enableInterrupt(true); + else + res = fts_enableInterrupt(false); + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_SETSCANMODE: + /* need to pass: scanMode option */ + if (numberParam >= 3) + res = setScanMode(cmd[1], cmd[2]); + else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_SAVEMPFLAG: + /* need to pass: mpflag */ + if (numberParam == 2) + res = saveMpFlag(cmd[1]); + else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_READCONFIG: + if (numberParam == 5) { /* need to pass: addr[0] + * addr[1] byteToRead1 + * byteToRead0 */ + byteToRead = ((funcToTest[3] << 8) | + funcToTest[4]); + readData = (u8 *)kmalloc(byteToRead * + sizeof(u8), + GFP_KERNEL); + res = readConfig((u16)((((u8)funcToTest[1] & + 0x00FF) << 8) + + ((u8)funcToTest[2] & + 0x00FF)), + readData, byteToRead); + size += (byteToRead * sizeof(u8)); + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_POLLFOREVENT: + if (numberParam >= 5) { /* need to pass: eventLength + * event[0] event[1] … + * event[eventLength-1] + * timeTowait1 timeTowait0 */ + temp = (int)funcToTest[1]; + if (numberParam == 5 + (temp - 1) && + temp != 0) { + readData = (u8 *)kmalloc( + FIFO_EVENT_SIZE * sizeof(u8), + GFP_KERNEL); + res = pollForEvent( + (int *)&funcToTest[2], temp, + readData, + ((funcToTest[temp + 2] & + 0x00FF) << 8) + + (funcToTest[temp + 3] & + 0x00FF)); + if (res >= OK) + res = OK; /* pollForEvent + * return the + * number of + * error found + * */ + size += (FIFO_EVENT_SIZE * sizeof(u8)); + byteToRead = FIFO_EVENT_SIZE; + } else { + pr_err("Wrong parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_SYSTEMRESET: + res = fts_system_reset(); + + break; + + case CMD_READSYSINFO: + if (numberParam == 2) /* need to pass: doRequest */ + res = readSysInfo(funcToTest[1]); + else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + + break; + + case CMD_CLEANUP:/* TOUCH ENABLE/DISABLE */ + if (numberParam == 2) /* need to pass: enableTouch */ + res = cleanUp(funcToTest[1]); + else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + + break; + + case CMD_GETFORCELEN: /* read number Tx channels */ + temp = getForceLen(); + if (temp < OK) + res = temp; + else { + size += (1 * sizeof(u8)); + res = OK; + } + break; + + case CMD_GETSENSELEN: /* read number Rx channels */ + temp = getSenseLen(); + if (temp < OK) + res = temp; + else { + size += (1 * sizeof(u8)); + res = OK; + } + break; + + + case CMD_GETMSFRAME: + if (numberParam == 2) { + pr_info("Get 1 MS Frame\n"); + /* setScanMode(SCAN_MODE_ACTIVE, 0xFF); + * mdelay(WAIT_FOR_FRESH_FRAMES); + * setScanMode(SCAN_MODE_ACTIVE, 0x00); + * mdelay(WAIT_AFTER_SENSEOFF); + */ + /* flushFIFO(); //delete the events related to + * some touch (allow to call this function while + * touching the screen without having a flooding + * of the FIFO) */ + res = getMSFrame3((MSFrameType)cmd[1], + &frameMS); + if (res < 0) + pr_err("Error while taking the MS frame... ERROR %08X\n", + res); + + else { + pr_info("The frame size is %d words\n", + res); + size += (res * sizeof(short) + 2); + /* +2 to add force and sense channels + * set res to OK because if getMSFrame + * is successful + * res = number of words read + */ + res = OK; + print_frame_short("MS frame =", + array1dTo2d_short( + frameMS.node_data, + frameMS.node_data_size, + frameMS.header.sense_node), + frameMS.header.force_node, + frameMS.header.sense_node); + } + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + + /*read self raw*/ + case CMD_GETSSFRAME: + if (numberParam == 2) { + /* pr_info("Get 1 SS Frame\n"); */ + /* flushFIFO(); //delete the events related to + * some touch (allow to call this function while + * touching the screen without having a flooding + * of the FIFO) */ + /* setScanMode(SCAN_MODE_ACTIVE, 0xFF); + * mdelay(WAIT_FOR_FRESH_FRAMES); + * setScanMode(SCAN_MODE_ACTIVE, 0x00); + * mdelay(WAIT_AFTER_SENSEOFF); + */ + res = getSSFrame3((SSFrameType)cmd[1], + &frameSS); + + if (res < OK) + pr_err("Error while taking the SS frame... ERROR %08X\n", + res); + + else { + pr_info("The frame size is %d words\n", + res); + size += (res * sizeof(short) + 2); + /* +2 to add force and sense channels + * set res to OK because if getMSFrame + * is successful + * res = number of words read + */ + res = OK; + print_frame_short("SS force frame =", + array1dTo2d_short( + frameSS.force_data, + frameSS.header.force_node, + 1), + frameSS.header.force_node, 1); + print_frame_short("SS sense frame =", + array1dTo2d_short( + frameSS.sense_data, + frameSS.header.sense_node, + frameSS.header.sense_node), + 1, + frameSS.header.sense_node); + } + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_GETSYNCFRAME: + /* need to pass: frameType (this parameter can be + * one of LOAD_SYNC_FRAME_X) + */ + if (numberParam == 2) { + pr_info("Reading Sync Frame...\n"); + res = getSyncFrame(cmd[1], &frameMS, &frameSS); + if (res < OK) + pr_err("Error while taking the Sync Frame frame... ERROR %08X\n", + res); + + else { + pr_info("The total frames size is %d words\n", + res); + size += (res * sizeof(short) + 4); + /* +4 to add force and sense channels + * for MS and SS. + * Set res to OK because if getSyncFrame + * is successful res = number of words + * read + */ + res = OK; + + print_frame_short("MS frame =", + array1dTo2d_short( + frameMS.node_data, + frameMS.node_data_size, + frameMS.header.sense_node), + frameMS.header.force_node, + frameMS.header.sense_node); + print_frame_short("SS force frame =", + array1dTo2d_short( + frameSS.force_data, + frameSS.header.force_node, + 1), + frameSS.header.force_node, + 1); + print_frame_short("SS sense frame =", + array1dTo2d_short( + frameSS.sense_data, + frameSS.header.sense_node, + frameSS.header.sense_node), + 1, + frameSS.header.sense_node); + } + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_REQCOMPDATA: /* request comp data */ + if (numberParam == 2) { + pr_info("Requesting Compensation Data\n"); + res = requestHDMDownload(cmd[1]); + + if (res < OK) + pr_err("Error requesting compensation data ERROR %08X\n", + res); + else + pr_info("Requesting Compensation Data Finished!\n"); + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_READCOMPDATAHEAD: /* read comp data header */ + if (numberParam == 2) { + pr_info("Requesting Compensation Data\n"); + res = requestHDMDownload(cmd[1]); + if (res < OK) + pr_err("Error requesting compensation data ERROR %08X\n", + res); + else { + pr_info("Requesting Compensation Data Finished!\n"); + res = readHDMHeader((u8)funcToTest[1], + &dataHead, &address); + if (res < OK) + pr_err("Read Compensation Data Header ERROR %08X\n", + res); + else { + pr_info("Read Compensation Data Header OK!\n"); + size += (1 * sizeof(u8)); + } + } + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + + case CMD_READMSCOMPDATA:/* read mutual comp data */ + if (numberParam == 2) { + pr_info("Get MS Compensation Data\n"); + res = readMutualSenseCompensationData(cmd[1], + &compData); + + if (res < OK) + pr_err("Error reading MS compensation data ERROR %08X\n", + res); + else { + pr_info("MS Compensation Data Reading Finished!\n"); + size = ((compData.node_data_size + 10) * + sizeof(i8)); + print_frame_i8("MS Data (Cx2) =", + array1dTo2d_i8( + compData.node_data, + compData.node_data_size, + compData.header.sense_node), + compData.header.force_node, + compData.header.sense_node); + } + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_READSSCOMPDATA: + if (numberParam == 2) { /* read self comp data */ + pr_info("Get SS Compensation Data...\n"); + res = readSelfSenseCompensationData(cmd[1], + &comData); + if (res < OK) + pr_err("Error reading SS compensation data ERROR %08X\n", + res); + else { + pr_info("SS Compensation Data Reading Finished!\n"); + size = ((comData.header.force_node + + comData.header.sense_node) * + 2 + 15) * + sizeof(i8); + print_frame_i8("SS Data Ix2_fm = ", + array1dTo2d_i8( + comData.ix2_fm, + comData.header.force_node, + comData.header.force_node), + 1, + comData.header.force_node); + print_frame_i8("SS Data Cx2_fm = ", + array1dTo2d_i8( + comData.cx2_fm, + comData.header.force_node, + comData.header.force_node), + 1, + comData.header.force_node); + print_frame_i8("SS Data Ix2_sn = ", + array1dTo2d_i8( + comData.ix2_sn, + comData.header.sense_node, + comData.header.sense_node), + 1, + comData.header.sense_node); + print_frame_i8("SS Data Cx2_sn = ", + array1dTo2d_i8( + comData.cx2_sn, + comData.header.sense_node, + comData.header.sense_node), + 1, + comData.header.sense_node); + } + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_READGOLDENMUTUAL: + if (numberParam != 1) { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + break; + } + + pr_info("Get Golden Mutual Raw data\n"); + + res = readGoldenMutualRawData(&gmRawData); + if (res < OK) { + pr_err("Err reading GM data %08X\n", res); + break; + } + + pr_info("GM data reading Finished!\n"); + + size = (6 + gmRawData.data_size) * sizeof(u16); + + print_frame_short("Golden Mutual Data =", + array1dTo2d_short( + gmRawData.data, + gmRawData.data_size, + gmRawData.hdr.ms_s_len), + gmRawData.hdr.ms_f_len, + gmRawData.hdr.ms_s_len); + break; + + case CMD_READTOTMSCOMPDATA: /* read mutual comp data */ + if (numberParam == 2) { + pr_info("Get TOT MS Compensation Data\n"); + res = readTotMutualSenseCompensationData(cmd[1], + &totCompData); + + if (res < OK) + pr_err("Error reading TOT MS compensation data ERROR %08X\n", + res); + else { + pr_info("TOT MS Compensation Data Reading Finished!\n"); + size = (totCompData.node_data_size * + sizeof(short) + 9); + print_frame_short("MS Data (TOT Cx) =", + array1dTo2d_short( + totCompData.node_data, + totCompData.node_data_size, + totCompData.header.sense_node), + totCompData.header.force_node, + totCompData.header.sense_node); + } + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_READTOTSSCOMPDATA: + if (numberParam == 2) { /* read self comp data */ + pr_info("Get TOT SS Compensation Data...\n"); + res = readTotSelfSenseCompensationData(cmd[1], + &totComData); + if (res < OK) + pr_err("Error reading TOT SS compensation data ERROR %08X\n", + res); + else { + pr_info("TOT SS Compensation Data Reading Finished!\n"); + size = ((totComData.header.force_node + + totComData.header.sense_node) * + 2 * + sizeof(short) + 9); + print_frame_u16("SS Data TOT Ix_fm = ", + array1dTo2d_u16( + totComData.ix_fm, + totComData.header.force_node, + totComData.header.force_node), + 1, + totComData.header.force_node); + print_frame_short( + "SS Data TOT Cx_fm = ", + array1dTo2d_short( + totComData.cx_fm, + totComData.header.force_node, + totComData.header.force_node), + 1, + totComData.header.force_node); + print_frame_u16("SS Data TOT Ix_sn = ", + array1dTo2d_u16( + totComData.ix_sn, + totComData.header.sense_node, + totComData.header.sense_node), + 1, + totComData.header.sense_node); + print_frame_short( + "SS Data TOT Cx_sn = ", + array1dTo2d_short( + totComData.cx_sn, + totComData.header.sense_node, + totComData.header.sense_node), + 1, + totComData.header.sense_node); + } + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_READSENSCOEFF: + /* read MS and SS Sensitivity Coefficients */ + pr_info("Get Sensitivity Calibration Coefficients...\n"); + res = readSensitivityCoefficientsData(&msCoeff, + &ssCoeff); + if (res < OK) + pr_err("Error reading Sensitivity Calibration Coefficients ERROR %08X\n", + res); + else { + pr_info("Sensitivity Calibration Coefficients Reading Finished!\n"); + size += (((msCoeff.node_data_size) + + ssCoeff.header.force_node + + ssCoeff.header.sense_node) * + sizeof(u8) + 4); + print_frame_u8("MS Sensitivity Coeff = ", + array1dTo2d_u8(msCoeff.ms_coeff, + msCoeff. + node_data_size, + msCoeff.header. + sense_node), + msCoeff.header.force_node, + msCoeff.header.sense_node); + print_frame_u8("SS Sensitivity Coeff force = ", + array1dTo2d_u8( + ssCoeff.ss_force_coeff, + ssCoeff.header. + force_node, 1), + ssCoeff.header.force_node, 1); + print_frame_u8("SS Sensitivity Coeff sense = ", + array1dTo2d_u8( + ssCoeff.ss_sense_coeff, + ssCoeff.header. + sense_node, + ssCoeff.header. + sense_node), 1, + ssCoeff.header.sense_node); + } + break; + + case CMD_GETFWVER: + size += (EXTERNAL_RELEASE_INFO_SIZE)*sizeof(u8); + break; + + case CMD_FLASHUNLOCK: + res = flash_unlock(); + if (res < OK) + pr_err("Impossible Unlock Flash ERROR %08X\n", + res); + else + pr_info("Flash Unlock OK!\n"); + break; + + case CMD_READFWFILE: + if (numberParam == 2) { /* read fw file */ + pr_info("Reading FW File...\n"); + res = readFwFile(info->board->fw_name, &fw, + funcToTest[1]); + if (res < OK) + pr_err("Error reading FW File ERROR %08X\n", + res); + else + pr_info("Read FW File Finished!\n"); + kfree(fw.data); + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_FLASHPROCEDURE: + if (numberParam == 3) { /* flashing procedure */ + pr_info("Starting Flashing Procedure...\n"); + res = flashProcedure(info->board->fw_name, + cmd[1], + cmd[2]); + if (res < OK) + pr_err("Error during flash procedure ERROR %08X\n", + res); + else + pr_info("Flash Procedure Finished!\n"); + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_FLASHERASEUNLOCK: + res = flash_erase_unlock(); + if (res < OK) + pr_err("Error during flash erase unlock... ERROR %08X\n", + res); + else + pr_info("Flash Erase Unlock Finished!\n"); + break; + + case CMD_FLASHERASEPAGE: + if (numberParam == 2) { /* need to pass: keep_cx */ + pr_info("Reading FW File...\n"); + res = readFwFile(info->board->fw_name, &fw, + funcToTest[1]); + if (res < OK) + pr_err("Error reading FW File ERROR" + "%08X\n", res); + else + pr_info("Read FW File Finished!\n"); + pr_info("Starting Flashing Page Erase...\n"); + res = flash_erase_page_by_page(cmd[1], &fw); + if (res < OK) + pr_err("Error during flash page erase... ERROR %08X\n", + res); + else + pr_info("Flash Page Erase Finished!\n"); + kfree(fw.data); + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + /*ITO TEST*/ + case CMD_ITOTEST: + frameMS.node_data = NULL; + res = production_test_ito(limits_file, &tests, + &frameMS); + + if (frameMS.node_data != NULL) { + size += (frameMS.node_data_size * + sizeof(short) + 2); + report = 1; + } + break; + + /*Initialization*/ + case CMD_INITTEST: + if (numberParam == 2) + res = production_test_initialization(cmd[1]); + + else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + + case CMD_MSRAWTEST: /* MS Raw DATA TEST */ + if (numberParam == 2) /* need to specify if stopOnFail + * */ + res = production_test_ms_raw(limits_file, + cmd[1], &tests); + else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_MSINITDATATEST:/* MS CX DATA TEST */ + if (numberParam == 2) /* need stopOnFail */ + res = production_test_ms_cx(limits_file, cmd[1], + &tests); + else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_SSRAWTEST: /* SS RAW DATA TEST */ + if (numberParam == 2) /* need stopOnFail */ + res = production_test_ss_raw(limits_file, + cmd[1], &tests); + else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_SSINITDATATEST:/* SS IX CX DATA TEST */ + if (numberParam == 2) /* need stopOnFail */ + res = production_test_ss_ix_cx(limits_file, + cmd[1], &tests); + else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + /*PRODUCTION TEST*/ + case CMD_MAINTEST: + if (numberParam >= 3) /* need to specify if stopOnFail + * saveInit and + * mpflag(optional) + */ + if (numberParam == 3) + res = production_test_main(limits_file, + cmd[1], + cmd[2], &tests, + MP_FLAG_OTHERS); + else + res = production_test_main(limits_file, + cmd[1], + cmd[2], &tests, + cmd[3]); + else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_FREELIMIT: + res = freeCurrentLimitsFile(); + break; + + case CMD_POWERCYCLE: + res = fts_chip_powercycle(info); + break; + + case CMD_GETLIMITSFILE: + /* need to pass: path(optional) return error code + + * number of byte read otherwise GUI could not now how + * many byte read */ + if (numberParam >= 1) { + lim.data = NULL; + lim.size = 0; + if (numberParam == 1) + res = getLimitsFile(limits_file, &lim); + else + res = getLimitsFile(path, &lim); + readData = lim.data; + fileSize = lim.size; + size += (fileSize * sizeof(u8)); + if (byte_call == 1) + size += sizeof(u32); /* transmit as + * first 4 bytes + * the size */ + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_GETLIMITSFILE_BYTE: + /* need to pass: byteToRead1 byteToRead0 */ + if (numberParam >= 3) { + lim.data = NULL; + lim.size = 0; + + u8ToU16_be(&cmd[1], &byteToRead); + addr = ((u64)byteToRead) * 4; /* number of + * words */ + + res = getLimitsFile(limits_file, &lim); + + readData = lim.data; + fileSize = lim.size; + + if (fileSize > addr) { + pr_err("Limits dimension expected by Host is less than actual size: expected = %d, real = %d\n", + byteToRead, fileSize); + res = ERROR_OP_NOT_ALLOW; + } + + size += (addr * sizeof(u8)); + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_GETFWFILE: + /* need to pass: from (optional) otherwise select the + * approach chosen at compile time */ + if (numberParam >= 1) { + if (numberParam == 1) + res = getFWdata(info->board->fw_name, + &readData, &fileSize); + else + res = getFWdata(path, &readData, + &fileSize); + + size += (fileSize * sizeof(u8)); + if (byte_call == 1) + size += sizeof(u32); /* transmit as + * first 4 bytes + * the size */ + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_GETFWFILE_BYTE: + /* need to pass: byteToRead1 byteToRead0 */ + if (numberParam == 3) { + u8ToU16_be(&cmd[1], &byteToRead); + addr = ((u64)byteToRead) * 4; /* number of + * words */ + res = getFWdata(info->board->fw_name, &readData, + &fileSize); + if (fileSize > addr) { + pr_err("FW dimension expected by Host is less than actual size: expected = %d, real = %d\n", + byteToRead, fileSize); + res = ERROR_OP_NOT_ALLOW; + } + + size += (addr * sizeof(u8)); /* return always + * the amount + * requested by + * host, if real + * size is + * smaller, the + * data are + * padded to + * zero */ + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + /* finish all the diagnostic command with a goto ERROR in order + * to skip the modification on driver_test_buff + * remember to set properly the limit and printed variables in + * order to make the seq_file logic to work */ + case CMD_DIAGNOSTIC: + index = 0; + size = 0; + fileSize = 256 * 1024 * sizeof(char); + driver_test_buff = (u8 *)kzalloc(fileSize, GFP_KERNEL); + readData = (u8 *)kmalloc((ERROR_DUMP_ROW_SIZE * + ERROR_DUMP_COL_SIZE) * + sizeof(u8), GFP_KERNEL); + if (driver_test_buff == NULL || readData == NULL) { + res = ERROR_ALLOC; + pr_err("Impossible allocate memory for buffers! ERROR %08X!\n", + res); + goto END; + } + j = scnprintf(&driver_test_buff[index], + fileSize - index, + "DIAGNOSTIC TEST:\n1) I2C Test: "); + index += j; + + res = fts_writeReadU8UX(FTS_CMD_HW_REG_R, + ADDR_SIZE_HW_REG, ADDR_DCHIP_ID, + (u8 *)&temp, 2, + DUMMY_HW_REG); + if (res < OK) { + pr_err("Error during I2C test: ERROR %08X!\n", + res); + j = scnprintf(&driver_test_buff[index], + fileSize - index, "ERROR %08X\n", + res); + index += j; + res = ERROR_OP_NOT_ALLOW; + goto END_DIAGNOSTIC; + } + + temp &= 0xFFFF; + pr_info("Chip ID = %04X!\n", temp); + j = scnprintf(&driver_test_buff[index], + fileSize - index, + "DATA = %04X, expected = %02X%02X\n", + temp, DCHIP_ID_1, + DCHIP_ID_0); + index += j; + if (temp != ((DCHIP_ID_1 << 8) | DCHIP_ID_0)) { + pr_err("Wrong CHIP ID, Diagnostic failed!\n"); + res = ERROR_OP_NOT_ALLOW; + goto END_DIAGNOSTIC; + } + + j = scnprintf(&driver_test_buff[index], + fileSize - index, + "Present Driver Mode: %08X\n", + info->mode); + index += j; + + j = scnprintf(&driver_test_buff[index], + fileSize - index, + "2) FW running: Sensing On..."); + index += j; + pr_info("Sensing On!\n"); + readData[0] = FTS_CMD_SCAN_MODE; + readData[1] = SCAN_MODE_ACTIVE; + readData[2] = 0x1; + fts_write_heap(readData, 3); + res = checkEcho(readData, 3); + if (res < OK) { + pr_err("No Echo received.. ERROR %08X !\n", + res); + j = scnprintf(&driver_test_buff[index], + fileSize - index, + "No echo found... ERROR %08X!\n", + res); + index += j; + goto END_DIAGNOSTIC; + } else { + pr_info("Echo FOUND... OK!\n"); + j = scnprintf(&driver_test_buff[index], + fileSize - index, + "Echo FOUND... OK!\n"); + index += j; + } + + pr_info("Reading Frames...!\n"); + j = scnprintf(&driver_test_buff[index], + fileSize - index, + "3) Read Frames:\n"); + index += j; + for (temp = 0; temp < DIAGNOSTIC_NUM_FRAME; temp++) { + pr_info("Iteration n. %d...\n", temp + 1); + j = scnprintf(&driver_test_buff[index], + fileSize - index, + "Iteration n. %d...\n", + temp + 1); + index += j; + for (addr = 0; addr < 3; addr++) { + switch (addr) { + case 0: + j = scnprintf( + &driver_test_buff[index], + fileSize - index, + "MS RAW FRAME ="); + index += j; + res |= getMSFrame3(MS_RAW, + &frameMS); + break; + case 2: + j = scnprintf( + &driver_test_buff[index], + fileSize - index, + "MS STRENGTH FRAME ="); + index += j; + res |= getMSFrame3(MS_STRENGTH, + &frameMS); + break; + case 1: + j = scnprintf( + &driver_test_buff[index], + fileSize - index, + "MS BASELINE FRAME ="); + index += j; + res |= getMSFrame3(MS_BASELINE, + &frameMS); + break; + } + if (res < OK) { + j = scnprintf( + &driver_test_buff[index], + fileSize - index, + "No data! ERROR %08X\n", + res); + index += j; + } else { + for (address = 0; address < + frameMS.node_data_size; + address++) { + if (address % + frameMS.header. + sense_node == 0) { + j = scnprintf( + &driver_test_buff + [index], + fileSize - + index, + "\n"); + index += j; + } + j = scnprintf( + &driver_test_buff + [index], + fileSize - index, + "%5d, ", + frameMS. + node_data[address]); + index += j; + } + j = scnprintf( + &driver_test_buff[index], + fileSize - index, "\n"); + index += j; + } + if (frameMS.node_data != NULL) + kfree(frameMS.node_data); + } + for (addr = 0; addr < 3; addr++) { + switch (addr) { + case 0: + j = scnprintf( + &driver_test_buff[index], + fileSize - index, + "SS RAW FRAME =\n"); + index += j; + res |= getSSFrame3(SS_RAW, + &frameSS); + break; + case 2: + j = scnprintf( + &driver_test_buff[index], + fileSize - index, + "SS STRENGTH FRAME =\n"); + index += j; + res |= getSSFrame3(SS_STRENGTH, + &frameSS); + break; + case 1: + j = scnprintf( + &driver_test_buff[index], + fileSize - index, + "SS BASELINE FRAME =\n"); + index += j; + res |= getSSFrame3(SS_BASELINE, + &frameSS); + break; + } + if (res < OK) { + j = scnprintf( + &driver_test_buff[index], + fileSize - index, + "No data! ERROR %08X\n", + res); + index += j; + } else { + int num; + short *data; + + num = frameSS.header.force_node; + data = frameSS.force_data; + for (address = 0; + address < num; + address++) { + j = scnprintf( + &driver_test_buff[index], + fileSize - index, + "%d\n", + data[address]); + index += j; + } + + num = frameSS.header.sense_node; + data = frameSS.sense_data; + for (address = 0; + address < num; + address++) { + j = scnprintf( + &driver_test_buff[index], + fileSize - index, + "%d, ", + data[address]); + index += j; + } + j = scnprintf( + &driver_test_buff[index], + fileSize - index, "\n"); + index += j; + } + if (frameSS.force_data != NULL) + kfree(frameSS.force_data); + if (frameSS.sense_data != NULL) + kfree(frameSS.sense_data); + } + } + + + pr_info("Reading error info...\n"); + j = scnprintf(&driver_test_buff[index], + fileSize - index, + "4) FW INFO DUMP: "); + index += j; + temp = dumpErrorInfo(readData, ERROR_DUMP_ROW_SIZE * + ERROR_DUMP_COL_SIZE); + /* OR to detect if there are failures also in the + * previous reading of frames and write the correct + * result */ + if (temp < OK) { + pr_err("Error during dump: ERROR %08X!\n", + res); + j = scnprintf(&driver_test_buff[index], + fileSize - index, "ERROR %08X\n", + temp); + index += j; + } else { + pr_info("DUMP OK!\n"); + for (temp = 0; temp < ERROR_DUMP_ROW_SIZE * + ERROR_DUMP_COL_SIZE; temp++) { + if (temp % ERROR_DUMP_COL_SIZE == 0) { + j = scnprintf( + &driver_test_buff[index], + fileSize - index, + "\n%2d - ", + temp / ERROR_DUMP_COL_SIZE); + index += j; + } + j = scnprintf(&driver_test_buff[index], + fileSize - index, "%02X ", + readData[temp]); + index += j; + } + } + res |= temp; + +END_DIAGNOSTIC: + if (res < OK) { + j = scnprintf(&driver_test_buff[index], + fileSize - index, + "\nRESULT = FAIL\n"); + index += j; + } else { + j = scnprintf(&driver_test_buff[index], + fileSize - index, + "\nRESULT = FINISHED\n"); + index += j; + } + /* the sting is already terminated with the null char by + * scnprintf */ + limit = index; + printed = 0; + goto ERROR; + break; + +#ifdef I2C_INTERFACE + case CMD_CHANGE_SAD: + res = changeSAD(cmd[1]); + break; +#endif + + case CMD_TRIGGER_FORCECAL: + cmd[0] = CAL_MS_TOUCH | CAL_SS_TOUCH; + cmd[1] = 0x00; + fts_enableInterrupt(false); + res = writeSysCmd(SYS_CMD_FORCE_CAL, cmd, 2); + res |= fts_enableInterrupt(true); + if (res < OK) + pr_err("can not trigger Force Cal! ERROR %08X\n", + res); + else + pr_info("MS and SS force cal triggered!\n"); + break; + + case CMD_BASELINE_ADAPTATION: + /* need to pass: enable */ + if (numberParam == 2) { + if (cmd[1] == 0x01) + pr_info("Enabling Baseline adaptation...\n"); + else { + pr_info("Disabling Baseline adaptation...\n"); + cmd[1] = 0x00; /* set to zero to + * disable baseline + * adaptation */ + } + + res = writeConfig(ADDR_CONFIG_AUTOCAL, &cmd[1], + 1); + if (res < OK) + pr_err("Baseline adaptation operation FAILED! ERROR %08X\n", + res); + else + pr_info("Baseline adaptation operation OK!\n"); + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_FREQ_HOP: + /* need to pass: enable */ + if (numberParam == 2) { + pr_info("Reading MNM register...\n"); + res = readConfig(ADDR_CONFIG_MNM, &cmd[2], 1); + if (res < OK) { + pr_err("Reading MNM register... ERROR %08X!\n", + res); + break; + } + + if (cmd[1] == 0x01) { + pr_info("Enabling Frequency Hopping... %02X => %02X\n", + cmd[2], cmd[2] | 0x01); + cmd[2] |= 0x01; /* set bit 0 to enable + * Frequency Hopping */ + } else { + pr_info("Disabling Frequency Hopping... %02X => %02X\n", + cmd[2], cmd[2] & (~0x01)); + cmd[2] &= (~0x01); /* reset bit 0 + * to disable + * Frequency + * Hopping */ + } + + res = writeConfig(ADDR_CONFIG_MNM, &cmd[2], 1); + if (res < OK) + pr_err("Frequency Hopping operation FAILED! ERROR %08X\n", + res); + else + pr_info("Frequency Hopping operation OK!\n"); + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_READ_SYNC_FRAME: + /* need to pass: frameType (this parameter can be + * LOAD_SYNC_FRAME_STRENGTH or LOAD_SYNC_FRAME_BASELINE) + * */ + if (numberParam == 2) { + pr_info("Reading Sync Frame...\n"); + res = getSyncFrame(cmd[1], &frameMS, &frameSS); + if (res < OK) + pr_err("Error while taking the Sync Frame frame... ERROR %08X\n", + res); + + else { + pr_info("The total frames size is %d words\n", + res); + size += (res * sizeof(short) + 4); + /* +4 to add force and sense channels + * for MS and SS + * set res to OK because if getSyncFrame + * is successful + * res = number of words read + */ + res = OK; + + print_frame_short("MS frame =", + array1dTo2d_short( + frameMS.node_data, + frameMS.node_data_size, + frameMS.header.sense_node), + frameMS.header.force_node, + frameMS.header.sense_node); + print_frame_short("SS force frame =", + array1dTo2d_short( + frameSS.force_data, + frameSS.header.force_node, + 1), + frameSS.header.force_node, 1); + print_frame_short("SS sense frame =", + array1dTo2d_short( + frameSS.sense_data, + frameSS.header.sense_node, + frameSS.header.sense_node), + 1, + frameSS.header.sense_node); + } + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_SET_OPERATING_FREQ: + /* need to pass: freq3 freq2 freq1 freq0 */ + if (numberParam == 5) { + res = fts_enableInterrupt(false); + if (res >= OK) { + pr_info("Setting Scan Freq...\n"); + u8ToU32_be(&cmd[1], &fileSize); + /* fileSize is used just as container + * variable, sorry for the name! */ + + res = setActiveScanFrequency(fileSize); + if (res < OK) + pr_err("Error while setting the scan frequency... ERROR %08X\n", + res); + else { + /* setActiveScan Frequency leave + * the chip in reset state but + * with the new scan freq set */ + /* need to enable the scan mode + * and re-enable the interrupts + * */ + res |= setScanMode( + SCAN_MODE_LOCKED, + LOCKED_ACTIVE); + /* this is a choice to force + * the IC to use the freq set */ + res |= fts_enableInterrupt( + true); + pr_info("Setting Scan Freq... res = %08X\n", + res); + } + } + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + + break; + + case CMD_TP_SENS_MODE: + /* need to pass: enter (optional)saveGain */ + if (numberParam >= 2) { + if (numberParam == 2) + cmd[2] = 0; /* by default never save + * the gain (used only + * when exit) */ + + res = tp_sensitivity_mode(cmd[1], cmd[2]); + if (res < OK) + pr_err("Error while setting TP Sens mode... ERROR %08X\n", + res); + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + + case CMD_TP_SENS_SET_SCAN_MODE: + /* need to pass: scan_type, enableGains */ + if (numberParam == 3) { + if (cmd[1] == 0x07) { /* To match the api for + * C2/F2 + */ + res = tp_sensitivity_set_scan_mode( + LOCKED_SINGLE_ENDED_ONLY_MUTUAL_0, + cmd[2]); + /* this force the IC to lock in a scan + * mode + */ + if (res < OK) + pr_err("Error while setting TP Sens scan mode... ERROR %08X\n", + res); + } else { + pr_err("Wrong parameter!\n"); + res = ERROR_OP_NOT_ALLOW; + } + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + + break; + + case CMD_TP_SENS_PRECAL_SS: + /* need to pass: target1 target0 percentage(optional) */ + if (numberParam >= 3) { + if (numberParam > 3) + temp = cmd[3]; + else + temp = SENS_TEST_PERC_TARGET_PRECAL; + + pr_info("Setting target = %d and percentage = %d\n", + (cmd[1] << 8 | cmd[2]), temp); + + res = tp_sensitivity_test_pre_cal_ss(&frameSS, + (cmd[1] << 8 | + cmd[2]), + temp); + if (res < OK) + pr_err("Error while setting the scan frequency... ERROR %08X\n", + res); + + if ((frameSS.force_data != NULL) && + (frameSS.sense_data != NULL)) { + size += ((frameSS.header.force_node + + frameSS.header.sense_node) * + sizeof(short) + 2); + /*make error code positive to print the + * frame*/ + res &= (~0x80000000); + } + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_TP_SENS_PRECAL_MS: + /* need to pass: target1 target0 calibrate + * percentage(optional) */ + if (numberParam >= 4) { + if (numberParam > 4) + temp = cmd[4]; + else + temp = SENS_TEST_PERC_TARGET_PRECAL; + + pr_info("Setting target = %d and percentage = %d\n", + (cmd[1] << 8 | cmd[2]), temp); + + res = tp_sensitivity_test_pre_cal_ms(&frameMS, + (cmd[1] << 8 | + cmd[2]), + temp); + if (res < OK) + pr_err("Error during TP Sensitivity Precal ... ERROR %08X\n", + res); + + if (cmd[3] != 0) { + pr_info("Computing gains with target = %d and saveGain = %d\n", + (cmd[1] << 8 | cmd[2]), 0); + temp = tp_sensitivity_compute_gains( + &frameMS, (cmd[1] << 8 | + cmd[2]), + 0); + if (temp < OK) + pr_err("Error during TP Sensitivity Calibration... ERROR %08X\n", + temp); + res |= temp; + } + + if (frameMS.node_data != NULL) { + size += (frameMS.node_data_size * + sizeof(short) + 2); + /*make error code positive to print the + * frame*/ + res &= (~0x80000000); + } + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_TP_SENS_POSTCAL_MS: + /* need to pass: target1 target0 executeTest + * percentage(optional) */ + if (numberParam >= 4) { + if (cmd[3] != 0) { + if (numberParam > 4) + temp = cmd[4]; + else + temp = + SENS_TEST_PERC_TARGET_POSTCAL; + } else + temp = -1; + + pr_info("Setting target = %d and percentage = %d\n", + (cmd[1] << 8 | cmd[2]), temp); + + res = tp_sensitivity_test_post_cal_ms(&frameMS, + &deltas, + (cmd[1] << 8 | + cmd[2]), + temp, + &meanNorm, + &meanEdge); + if (res < OK) + pr_err("Error during TP Sensitivity Post Cal ... ERROR %08X\n", + res); + + /* processing for a proper printing on the shell + * */ + if ((frameMS.node_data != NULL) && + (deltas.node_data != NULL)) { + size += ((frameMS.node_data_size + + deltas.node_data_size) * + sizeof(short) + + 2 + 8);/* +2 force and + * sense len, +8 + * mean_normal/edge + * */ + /*make error code positive to print the + * frame*/ + res &= (~0x80000000); + } + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + + case CMD_TP_SENS_STD: + /* need to pass: numFrames */ + if (numberParam >= 2) { + res = tp_sensitivity_test_std_ms(cmd[1], + &frameMS); + if (res < OK) + pr_err("Error during TP Sensitivity STD... ERROR %08X\n", + res); + + /* processing for a proper printing on the shell + * */ + if (frameMS.node_data != NULL) { + size += ((frameMS.node_data_size) * + sizeof(short) + 2); + /* +2 force and sense len */ + /*make error code positive to print the + * frame*/ + res &= (~0x80000000); + } + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_FORCE_TOUCH_ACTIVE: + /* Single parameter indicates force touch state */ + if (numberParam == 2) { + if (cmd[1] > 1) { + pr_err("Parameter should be 1 or 0\n"); + res = ERROR_OP_NOT_ALLOW; + } else { + pr_info("FTS_BUS_REF_FORCE_ACTIVE: %s\n", + cmd[1] ? "ON" : "OFF"); + fts_set_bus_ref(info, + FTS_BUS_REF_FORCE_ACTIVE, + cmd[1]); + res = OK; + if (cmd[1]) + __pm_stay_awake(info->wakesrc); + else + __pm_relax(info->wakesrc); + } + } else { + pr_err("Wrong number of parameters!\n"); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_INFOBLOCK_STATUS: + res = fts_system_reset(); + if (res >= OK) { + res = pollForErrorType(error_to_search, 2); + if (res < OK) { + pr_err("No info block corruption!\n"); + res = OK; + } else { + pr_info("Info block errors found!\n"); + res = ERROR_INFO_BLOCK; + } + } + break; + + default: + pr_err("COMMAND ID NOT VALID!!!\n"); + res = ERROR_OP_NOT_ALLOW; + break; + } + + /* res2 = fts_enableInterrupt(true); + * the interrupt was disabled on purpose in this node because it + * can be used for testing procedure and between one step and + * another the interrupt wan to be kept disabled + * if (res2 < 0) { + * pr_err("stm_driver_test_show: ERROR %08X\n", + * (res2 | ERROR_ENABLE_INTER)); + * }*/ + } else { + pr_err("NO COMMAND SPECIFIED!!! do: 'echo [cmd_code] [args] > stm_fts_cmd' before looking for result!\n"); + res = ERROR_OP_NOT_ALLOW; + } + +END: /* here start the reporting phase, assembling the data to send in the + * file node */ + if (driver_test_buff != NULL) { + pr_info("Consecutive echo on the file node, free the buffer with the previous result\n"); + kfree(driver_test_buff); + } + + if (byte_call == 0) { + size *= 2; + size += 2; /* add \n and \0 (terminator char) */ + } else { + if (bin_output != 1) { + size *= 2; /* need to code each byte as HEX string */ + size -= 1; /* start byte is just one, the extra + * byte of end byte taken by \n */ + } else + size += 1; /* add \n */ + } + + pr_info("Size = %d\n", size); + driver_test_buff = (u8 *)kzalloc(size, GFP_KERNEL); + pr_info("Finish to allocate memory!\n"); + if (driver_test_buff == NULL) { + pr_err("Unable to allocate driver_test_buff! ERROR %08X\n", + ERROR_ALLOC); + goto ERROR; + } + + if (byte_call == 0) { + index = 0; + index += scnprintf(&driver_test_buff[index], + size - index, "{ "); + index += scnprintf(&driver_test_buff[index], + size - index, "%08X", res); + if (res >= OK || report) { + /*all the other cases are already fine printing only the + * res.*/ + switch (funcToTest[0]) { + case CMD_VERSION: + case CMD_READ: + case CMD_WRITEREAD: + case CMD_WRITETHENWRITEREAD: + case CMD_WRITEREADU8UX: + case CMD_WRITEU8UXTHENWRITEREADU8UX: + case CMD_READCONFIG: + case CMD_POLLFOREVENT: + /* pr_err("Data = "); */ + if (mess.dummy == 1) + j = 1; + else + j = 0; + for (; j < byteToRead + mess.dummy; j++) { + /* pr_err("%02X ", readData[j]); */ + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X", readData[j]); + /* this approach is much more faster */ + } + /* pr_err("\n"); */ + break; + case CMD_GETFWFILE: + case CMD_GETLIMITSFILE: + pr_info("Start To parse!\n"); + for (j = 0; j < fileSize; j++) { + /* pr_err("%02X ", readData[j]); */ + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X", readData[j]); + } + pr_info("Finish to parse!\n"); + break; + case CMD_GETFORCELEN: + case CMD_GETSENSELEN: + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + (u8)temp); + break; + case CMD_GETMSFRAME: + case CMD_TP_SENS_PRECAL_MS: + case CMD_TP_SENS_POSTCAL_MS: + case CMD_TP_SENS_STD: + case CMD_ITOTEST: + + if (frameMS.node_data == NULL) + break; + + if (res != OK) + driver_test_buff[2] = '8'; + /* convert back error code to negative */ + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + (u8)frameMS.header.force_node); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + (u8)frameMS.header.sense_node); + + for (j = 0; j < frameMS.node_data_size; j++) { + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X%02X", + (frameMS.node_data[j] & 0xFF00) >> 8, + frameMS.node_data[j] & 0xFF); + } + + kfree(frameMS.node_data); + + if (funcToTest[0] == CMD_TP_SENS_POSTCAL_MS) { + /* print also mean and deltas */ + for (j = 0; j < deltas.node_data_size; + j++) { + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X%02X", + (deltas.node_data[j] & + 0xFF00) >> 8, + deltas.node_data[j] & + 0xFF); + } + kfree(deltas.node_data); + + index += scnprintf( + &driver_test_buff[index], + size - index, + "%08X", meanNorm); + + index += scnprintf( + &driver_test_buff[index], + size - index, + "%08X", meanEdge); + } + break; + case CMD_GETSSFRAME: + case CMD_TP_SENS_PRECAL_SS: + if (res != OK) + driver_test_buff[2] = '8'; + /* convert back error code to negative */ + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + (u8)frameSS.header.force_node); + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + (u8)frameSS.header.sense_node); + /* Copying self raw data Force */ + for (j = 0; j < frameSS.header.force_node; + j++) { + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X%02X", + (frameSS.force_data[j] & 0xFF00) >> 8, + frameSS.force_data[j] & 0xFF); + } + + /* Copying self raw data Sense */ + for (j = 0; j < frameSS.header.sense_node; + j++) { + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X%02X", + (frameSS.sense_data[j] & 0xFF00) >> 8, + frameSS.sense_data[j] & 0xFF); + } + + kfree(frameSS.force_data); + kfree(frameSS.sense_data); + break; + + case CMD_GETSYNCFRAME: + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + (u8)frameMS.header.force_node); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + (u8)frameMS.header.sense_node); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + (u8)frameSS.header.force_node); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + (u8)frameSS.header.sense_node); + + /* Copying mutual data */ + for (j = 0; j < frameMS.node_data_size; j++) { + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X%02X", + (frameMS.node_data[j] & + 0xFF00) >> 8, + frameMS.node_data[j] & 0xFF); + } + + /* Copying self data Force */ + for (j = 0; j < frameSS.header.force_node; + j++) { + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X%02X", + (frameSS.force_data[j] & + 0xFF00) >> 8, + frameSS.force_data[j] & 0xFF); + } + + /* Copying self data Sense */ + for (j = 0; j < frameSS.header.sense_node; + j++) { + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X%02X", + (frameSS.sense_data[j] & + 0xFF00) >> 8, + frameSS.sense_data[j] & 0xFF); + } + + kfree(frameMS.node_data); + kfree(frameSS.force_data); + kfree(frameSS.sense_data); + break; + + case CMD_READMSCOMPDATA: + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + (u8)compData.header.type); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + (u8)compData.header.force_node); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + (u8)compData.header.sense_node); + + /* Cpying CX1 value */ + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + compData.cx1 & 0xFF); + + /* Copying CX2 values */ + for (j = 0; j < compData.node_data_size; j++) { + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X", + compData.node_data[j] & 0xFF); + } + + kfree(compData.node_data); + break; + + case CMD_READSSCOMPDATA: + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + (u8)comData.header.type); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + comData.header.force_node); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + comData.header.sense_node); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + comData.f_ix1 & 0xFF); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + comData.s_ix1 & 0xFF); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + comData.f_cx1 & 0xFF); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + comData.s_cx1 & 0xFF); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + comData.f_ix0 & 0xFF); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + comData.s_ix0 & 0xFF); + + /* Copying IX2 Force */ + for (j = 0; j < comData.header.force_node; + j++) { + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X", + comData.ix2_fm[j] & 0xFF); + } + + /* Copying IX2 Sense */ + for (j = 0; j < comData.header.sense_node; + j++) { + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X", + comData.ix2_sn[j] & 0xFF); + } + + /* Copying CX2 Force */ + for (j = 0; j < comData.header.force_node; + j++) { + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X", + comData.cx2_fm[j] & 0xFF); + } + + /* Copying CX2 Sense */ + for (j = 0; j < comData.header.sense_node; + j++) { + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X", + comData.cx2_sn[j] & 0xFF); + } + + kfree(comData.ix2_fm); + kfree(comData.ix2_sn); + kfree(comData.cx2_fm); + kfree(comData.cx2_sn); + break; + + + case CMD_READGOLDENMUTUAL: + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + (u8)gmRawData.hdm_hdr.type); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + gmRawData.hdr.ms_f_len); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + gmRawData.hdr.ms_s_len); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + gmRawData.hdr.ss_f_len); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + gmRawData.hdr.ss_s_len); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + gmRawData.hdr.ms_k_len); + + /* Copying Golden Mutual raw values */ + for (j = 0; j < gmRawData.data_size; j++) { + index += scnprintf( + &driver_test_buff[index], + size - index, "%04X", + (u16)gmRawData.data[j]); + } + + kfree(gmRawData.data); + break; + + case CMD_READTOTMSCOMPDATA: + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + (u8)totCompData.header.type); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + (u8)totCompData.header.force_node); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + (u8)totCompData.header.sense_node); + + /* Copying TOT CX values */ + for (j = 0; j < totCompData.node_data_size; + j++) { + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X%02X", + (totCompData.node_data[j] & + 0xFF00) >> 8, + totCompData.node_data[j] & + 0xFF); + } + + kfree(totCompData.node_data); + break; + + case CMD_READTOTSSCOMPDATA: + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + (u8)totComData.header.type); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + totComData.header.force_node); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + totComData.header.sense_node); + + /* Copying TOT IX Force */ + for (j = 0; j < totComData.header.force_node; + j++) { + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X%02X", + (totComData.ix_fm[j] & + 0xFF00) >> 8, + totComData.ix_fm[j] & + 0xFF); + } + + /* Copying TOT IX Sense */ + for (j = 0; j < totComData.header.sense_node; + j++) { + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X%02X", + (totComData.ix_sn[j] & + 0xFF00) >> 8, + totComData.ix_sn[j] & + 0xFF); + } + + /* Copying TOT CX Force */ + for (j = 0; j < totComData.header.force_node; + j++) { + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X%02X", + (totComData.cx_fm[j] & + 0xFF00) >> 8, + totComData.cx_fm[j] & + 0xFF); + } + + /* Copying CX2 Sense */ + for (j = 0; j < totComData.header.sense_node; + j++) { + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X%02X", + (totComData.cx_sn[j] & + 0xFF00) >> 8, + totComData.cx_sn[j] & + 0xFF); + } + + kfree(totComData.ix_fm); + kfree(totComData.ix_sn); + kfree(totComData.cx_fm); + kfree(totComData.cx_sn); + break; + + case CMD_READSENSCOEFF: + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + (u8)msCoeff.header.force_node); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + (u8)msCoeff.header.sense_node); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + (u8)ssCoeff.header.force_node); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + (u8)ssCoeff.header.sense_node); + + /* Copying MS Coefficients */ + for (j = 0; j < msCoeff.node_data_size; j++) { + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X", + msCoeff.ms_coeff[j] & 0xFF); + } + + /* Copying SS force Coefficients */ + for (j = 0; j < ssCoeff.header.force_node; + j++) { + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X", + ssCoeff.ss_force_coeff[j] & + 0xFF); + } + + /* Copying SS sense Coefficients */ + for (j = 0; j < ssCoeff.header.sense_node; + j++) { + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X", + ssCoeff.ss_sense_coeff[j] & + 0xFF); + } + + kfree(msCoeff.ms_coeff); + kfree(ssCoeff.ss_force_coeff); + kfree(ssCoeff.ss_sense_coeff); + break; + + case CMD_GETFWVER: + for (j = 0; j < EXTERNAL_RELEASE_INFO_SIZE; + j++) { + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X", + systemInfo.u8_releaseInfo[j]); + } + break; + + case CMD_READCOMPDATAHEAD: + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + dataHead.type); + break; + + case CMD_READ_SYNC_FRAME: + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + (u8)frameMS.header.force_node); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + (u8)frameMS.header.sense_node); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + (u8)frameSS.header.force_node); + + index += scnprintf(&driver_test_buff[index], + size - index, "%02X", + (u8)frameSS.header.sense_node); + + /* Copying mutual data */ + for (j = 0; j < frameMS.node_data_size; j++) { + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X%02X", + (frameMS.node_data[j] & + 0xFF00) >> 8, + frameMS.node_data[j] & + 0xFF); + } + + /* Copying self data Force */ + for (j = 0; j < frameSS.header.force_node; + j++) { + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X%02X", + (frameSS.force_data[j] & + 0xFF00) >> 8, + frameSS.force_data[j] & + 0xFF); + } + + + /* Copying self data Sense */ + for (j = 0; j < frameSS.header.sense_node; + j++) { + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X%02X", + (frameSS.sense_data[j] & + 0xFF00) >> 8, + frameSS.sense_data[j] & + 0xFF); + } + + kfree(frameMS.node_data); + kfree(frameSS.force_data); + kfree(frameSS.sense_data); + break; + + + default: + break; + } + } + + index += scnprintf(&driver_test_buff[index], + size - index, " }\n"); + limit = size - 1;/* avoid to print \0 in the shell */ + printed = 0; + } else { + /* start byte */ + driver_test_buff[index++] = MESSAGE_START_BYTE; + if (bin_output == 1) { + /* msg_size */ + driver_test_buff[index++] = (size & 0xFF00) >> 8; + driver_test_buff[index++] = (size & 0x00FF); + /* counter id */ + driver_test_buff[index++] = + (mess.counter & 0xFF00) >> 8; + driver_test_buff[index++] = (mess.counter & 0x00FF); + /* action */ + driver_test_buff[index++] = (mess.action & 0xFF00) >> 8; + driver_test_buff[index++] = (mess.action & 0x00FF); + /* error */ + driver_test_buff[index++] = (res & 0xFF00) >> 8; + driver_test_buff[index++] = (res & 0x00FF); + } else { + if (funcToTest[0] == CMD_GETLIMITSFILE_BYTE || + funcToTest[0] == CMD_GETFWFILE_BYTE) + index += scnprintf(&driver_test_buff[index], + size - index, + "%02X%02X", + (((fileSize + 3) / 4) & 0xFF00) >> 8, + ((fileSize + 3) / 4) & 0x00FF); + else + index += scnprintf(&driver_test_buff[index], + size - index, + "%02X%02X", (size & 0xFF00) >> 8, + size & 0xFF); + + index += scnprintf(&driver_test_buff[index], + size - index, "%04X", + (u16)mess.counter); + index += scnprintf(&driver_test_buff[index], + size - index, "%04X", + (u16)mess.action); + index += scnprintf(&driver_test_buff[index], + size - index, + "%02X%02X", (res & 0xFF00) >> 8, + res & 0xFF); + } + + switch (funcToTest[0]) { + case CMD_VERSION_BYTE: + case CMD_READ_BYTE: + case CMD_WRITEREAD_BYTE: + case CMD_WRITETHENWRITEREAD_BYTE: + case CMD_WRITEREADU8UX_BYTE: + case CMD_WRITEU8UXTHENWRITEREADU8UX_BYTE: + if (bin_output == 1) { + if (mess.dummy == 1) + memcpy(&driver_test_buff[index], + &readData[1], byteToRead); + else + memcpy(&driver_test_buff[index], + readData, byteToRead); + index += byteToRead; + } else { + j = mess.dummy; + for (; j < byteToRead + mess.dummy; j++) + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X", + (u8)readData[j]); + } + break; + + case CMD_GETLIMITSFILE_BYTE: + case CMD_GETFWFILE_BYTE: + if (bin_output == 1) { + /* override the msg_size with dimension in words + * */ + driver_test_buff[1] = ( + ((fileSize + 3) / 4) & 0xFF00) >> 8; + driver_test_buff[2] = ( + ((fileSize + 3) / 4) & 0x00FF); + + if (readData != NULL) + memcpy(&driver_test_buff[index], + readData, fileSize); + else + pr_err("readData = NULL... returning junk data!"); + index += addr; /* in this case the byte to read + * are stored in addr because it + * is a u64 end byte need to be + * inserted at the end of the + * padded memory */ + } else { + /* snprintf(&driver_test_buff[1], 3, "%02X", + * (((fileSize + 3) / 4)&0xFF00) >> 8); */ + /* snprintf(&driver_test_buff[3], 3, "%02X", + * ((fileSize + 3) / 4)&0x00FF); */ + for (j = 0; j < fileSize; j++) + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X", + (u8)readData[j]); + for (; j < addr; j++) + index += scnprintf( + &driver_test_buff[index], + size - index, + "%02X", 0); /* pad memory + * with 0x00 */ + } + break; + default: + break; + } + + index += scnprintf(&driver_test_buff[index], + size - index, "%c\n", MESSAGE_END_BYTE); + /*for(j=0; j<size; j++){ + * pr_err("%c", driver_test_buff[j]); + * }*/ + limit = size; + printed = 0; + } +ERROR: + numberParam = 0;/* need to reset the number of parameters in order to + * wait the next command, comment if you want to repeat + * the last command sent just doing a cat */ + + /* pr_err(0,"numberParameters = %d\n", numberParam); */ + + kfree(readData); + kfree(cmd); + kfree(funcToTest); + kfree(pbuf); + + fts_set_bus_ref(info, FTS_BUS_REF_SYSFS, false); + +exit: + return count; +} + +/** @}*/ + +/** + * file_operations struct which define the functions for the canonical + * operation on a device file node (open. read, write etc.) + */ +static struct file_operations fts_driver_test_ops = { + .open = fts_driver_test_open, + .read = seq_read, + .write = fts_driver_test_write, + .llseek = seq_lseek, + .release = fts_driver_test_release +}; + +/*****************************************************************************/ + +/** + * This function is called in the probe to initialize and create the directory + * proc/fts and the driver test file node DRIVER_TEST_FILE_NODE into the /proc + * file system + * @return OK if success or an error code which specify the type of error + */ +int fts_proc_init(void) +{ + struct proc_dir_entry *entry; + + int retval = 0; + + + fts_dir = proc_mkdir_data("fts", 0777, NULL, NULL); + if (fts_dir == NULL) { /* directory creation failed */ + retval = -ENOMEM; + goto out; + } + + entry = proc_create(DRIVER_TEST_FILE_NODE, 0777, fts_dir, + &fts_driver_test_ops); + + if (entry) + pr_info("%s: proc entry CREATED!\n", __func__); + else { + pr_err("%s: error creating proc entry!\n", __func__); + retval = -ENOMEM; + goto badfile; + } + return OK; +badfile: + remove_proc_entry("fts", NULL); +out: + return retval; +} + +/** + * Delete and Clean from the file system, all the references to the driver test + * file node + * @return OK + */ +int fts_proc_remove(void) +{ + remove_proc_entry(DRIVER_TEST_FILE_NODE, fts_dir); + remove_proc_entry("fts", NULL); + return OK; +} |