From a4cbe9b9fe914c4618e2160437c6e10198bf40f6 Mon Sep 17 00:00:00 2001 From: Mohammed ELNaggar Date: Sat, 8 Feb 2020 16:12:34 +0200 Subject: [PATCH] drivers:input:touchscreen:Nvt-Display:imporved as v6 --- drivers/input/touchscreen/nt36xxx/nt36xxx.c | 119 +++++++++++++----- drivers/input/touchscreen/nt36xxx/nt36xxx.h | 21 +++- .../touchscreen/nt36xxx/nt36xxx_ext_proc.c | 12 +- .../touchscreen/nt36xxx/nt36xxx_fw_update.c | 14 +-- .../touchscreen/nt36xxx/nt36xxx_mem_map.h | 2 +- .../touchscreen/nt36xxx/nt36xxx_mp_ctrlram.c | 2 +- .../touchscreen/nt36xxx/nt36xxx_mp_ctrlram.h | 2 +- 7 files changed, 123 insertions(+), 49 deletions(-) diff --git a/drivers/input/touchscreen/nt36xxx/nt36xxx.c b/drivers/input/touchscreen/nt36xxx/nt36xxx.c index 36f8f7bdad7c..9e97a1a09fff 100644 --- a/drivers/input/touchscreen/nt36xxx/nt36xxx.c +++ b/drivers/input/touchscreen/nt36xxx/nt36xxx.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2010 - 2017 Novatek, Inc. - * Copyright (C) 2018 XiaoMi, Inc. + * Copyright (C) 2019 XiaoMi, Inc. * * $Revision: 20544 $ * $Date: 2017-12-20 11:08:15 +0800 (週三, 20 十二月 2017) $ @@ -47,8 +47,8 @@ static struct delayed_work nvt_esd_check_work; static struct workqueue_struct *nvt_esd_check_wq; static unsigned long irq_timer; -uint8_t esd_check = false; -uint8_t esd_retry = 0; +uint8_t esd_check; +uint8_t esd_retry; uint8_t esd_retry_max = 5; #endif /* #if NVT_TOUCH_ESD_PROTECT */ @@ -206,11 +206,11 @@ int32_t CTP_I2C_WRITE(struct i2c_client *client, uint16_t address, uint8_t *buf, *******************************************************/ void nvt_sw_reset_idle(void) { - uint8_t buf[4] = {0}; + uint8_t buf[4]={0}; /*---write i2c cmds to reset idle---*/ - buf[0] = 0x00; - buf[1] = 0xA5; + buf[0]=0x00; + buf[1]=0xA5; CTP_I2C_WRITE(ts->client, I2C_HW_Address, buf, 2); msleep(15); @@ -316,7 +316,7 @@ int32_t nvt_check_fw_status(void) if (i >= retry) { NVT_ERR("failed, i=%d, buf[1]=0x%02X\n", i, buf[1]); - return -1; + return -EPERM; } else { return 0; } @@ -593,7 +593,7 @@ static const struct file_operations nvt_flash_fops = { *******************************************************/ static int32_t nvt_flash_proc_init(void) { - NVT_proc_entry = proc_create(DEVICE_NAME, 0444, NULL, &nvt_flash_fops); + NVT_proc_entry = proc_create(DEVICE_NAME, 0444, NULL,&nvt_flash_fops); if (NVT_proc_entry == NULL) { NVT_ERR("Failed!\n"); return -ENOMEM; @@ -774,6 +774,11 @@ static int nvt_parse_dt(struct device *dev) NVT_LOG("ibb_reg_name = %s\n", name); } +#ifdef NVT_TOUCH_COUNT_DUMP + ts->dump_click_count = + of_property_read_bool(np, "novatek,dump-click-count"); +#endif + retval = of_property_read_u32(np, "novatek,config-array-size", (u32 *) & ts->config_array_size); if (retval) { @@ -860,7 +865,6 @@ static const char *nvt_get_config(struct nvt_ts_data *ts) NVT_LOG("Choose config %d: %s", i, ts->config_array[i].nvt_cfg_name); ts->current_index = i; - return ts->config_array[i].nvt_cfg_name; } @@ -1026,7 +1030,7 @@ static uint8_t nvt_fw_recovery(uint8_t *point_data) uint8_t detected = true; /* check pattern */ - for (i = 1 ; i < 7 ; i++) { + for (i=1 ; i<7 ; i++) { if (point_data[i] != 0x77) { detected = false; break; @@ -1066,11 +1070,9 @@ static void nvt_switch_mode_work(struct work_struct *work) struct nvt_mode_switch *ms = container_of(work, struct nvt_mode_switch, switch_mode_work); struct nvt_ts_data *data = ms->nvt_data; unsigned char value = ms->mode; - char ch[64] = {0x0,}; if (value >= INPUT_EVENT_WAKUP_MODE_OFF && value <= INPUT_EVENT_WAKUP_MODE_ON) { data->gesture_enabled = value - INPUT_EVENT_WAKUP_MODE_OFF; - snprintf(ch, sizeof(ch), "%s", data->gesture_enabled ? "enabled" : "disabled"); } else { NVT_ERR("Does not support touch mode %d\n", value); } @@ -1149,6 +1151,13 @@ static void nvt_ts_work_func(struct work_struct *work) NVT_ERR("CTP_I2C_READ failed.(%d)\n", ret); goto XFER_ERROR; } +/* + //--- dump I2C buf --- + for (i = 0; i < 10; i++) { + printk("%02X %02X %02X %02X %02X %02X ", point_data[1+i*6], point_data[2+i*6], point_data[3+i*6], point_data[4+i*6], point_data[5+i*6], point_data[6+i*6]); + } + printk("\n"); +*/ #if NVT_TOUCH_ESD_PROTECT if (nvt_fw_recovery(point_data)) { @@ -1273,10 +1282,7 @@ static irqreturn_t nvt_ts_irq_handler(int32_t irq, void *dev_id) if (bTouchIsAwake == 0) { dev_dbg(&ts->client->dev, "%s gesture wakeup\n", __func__); } - - pm_qos_update_request(&ts->pm_qos_req, 100); queue_work(nvt_wq, &ts->nvt_work); - pm_qos_update_request(&ts->pm_qos_req, PM_QOS_DEFAULT_VALUE); return IRQ_HANDLED; } @@ -1312,13 +1318,13 @@ void nvt_stop_crc_reboot(void) for (retry = 5; retry > 0; retry--) { /*---write i2c cmds to reset idle : 1st---*/ - buf[0] = 0x00; - buf[1] = 0xA5; + buf[0]=0x00; + buf[1]=0xA5; CTP_I2C_WRITE(ts->client, I2C_HW_Address, buf, 2); /*---write i2c cmds to reset idle : 2rd---*/ - buf[0] = 0x00; - buf[1] = 0xA5; + buf[0]=0x00; + buf[1]=0xA5; CTP_I2C_WRITE(ts->client, I2C_HW_Address, buf, 2); msleep(1); @@ -1439,6 +1445,14 @@ static int8_t nvt_ts_check_chip_ver_trim(void) return ret; } +#ifdef NVT_TOUCH_COUNT_DUMP +static ssize_t nvt_touch_suspend_notify_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", !bTouchIsAwake); +} +static DEVICE_ATTR(touch_suspend_notify, (S_IRUGO | S_IRGRP), nvt_touch_suspend_notify_show, NULL); +#endif static int32_t nvt_ts_suspend(struct device *dev); static int32_t nvt_ts_resume(struct device *dev); @@ -1594,7 +1608,6 @@ static struct attribute *nvt_attr_group[] = { &dev_attr_panel_vendor.attr, &dev_attr_panel_color.attr, &dev_attr_panel_display.attr, - NULL, }; /******************************************************* @@ -1610,6 +1623,7 @@ static int32_t nvt_ts_probe(struct i2c_client *client, const struct i2c_device_i #if ((TOUCH_KEY_NUM > 0) || WAKEUP_GESTURE) int32_t retry = 0; #endif + char *tp_maker = NULL; NVT_LOG("start\n"); @@ -1750,9 +1764,6 @@ static int32_t nvt_ts_probe(struct i2c_client *client, const struct i2c_device_i /* we should enable the reg for lpwg mode */ /*nvt_enable_reg(ts, true);*/ - pm_qos_add_request(&ts->pm_qos_req, PM_QOS_CPU_DMA_LATENCY, - PM_QOS_DEFAULT_VALUE); - /*---set int-pin & request irq---*/ client->irq = gpio_to_irq(ts->irq_gpio); if (client->irq) { @@ -1771,7 +1782,6 @@ static int32_t nvt_ts_probe(struct i2c_client *client, const struct i2c_device_i NVT_LOG("request irq %d succeed\n", client->irq); } } - update_hardware_info(TYPE_TOUCH, 5); ret = nvt_get_lockdown_info(ts->lockdown_info); @@ -1781,10 +1791,13 @@ static int32_t nvt_ts_probe(struct i2c_client *client, const struct i2c_device_i NVT_ERR("Lockdown:0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x\n", ts->lockdown_info[0], ts->lockdown_info[1], ts->lockdown_info[2], ts->lockdown_info[3], ts->lockdown_info[4], ts->lockdown_info[5], ts->lockdown_info[6], ts->lockdown_info[7]); - update_hardware_info(TYPE_TP_MAKER, ts->lockdown_info[0] - 0x30); } ts->fw_name = nvt_get_config(ts); + tp_maker = kzalloc(20, GFP_KERNEL); + if (tp_maker == NULL) + NVT_ERR("fail to alloc vendor name memory\n"); + device_init_wakeup(&client->dev, 1); ts->dev_pm_suspend = false; init_completion(&ts->dev_pm_suspend_completion); @@ -1857,11 +1870,29 @@ static int32_t nvt_ts_probe(struct i2c_client *client, const struct i2c_device_i NVT_ERR("Cannot create sysfs structure!\n"); } +#ifdef NVT_TOUCH_COUNT_DUMP + if (ts->nvt_tp_class == NULL) + ts->nvt_tp_class = class_create(THIS_MODULE, "touch"); + ts->nvt_touch_dev = device_create(ts->nvt_tp_class, NULL, 0x62, ts, "touch_suspend_notify"); + + if (IS_ERR(ts->nvt_touch_dev)) { + NVT_ERR("ERROR: Failed to create device for the sysfs!\n"); + goto err_register_tp_class; + } + + dev_set_drvdata(ts->nvt_touch_dev, ts); + ret = sysfs_create_file(&ts->nvt_touch_dev->kobj, &dev_attr_touch_suspend_notify.attr); + + if (ret) { + NVT_ERR("ERROR: Failed to create sysfs group!\n"); + goto err_register_tp_class; + } +#endif ts->event_wq = alloc_workqueue("nvt-event-queue", WQ_UNBOUND | WQ_HIGHPRI | WQ_CPU_INTENSIVE, 1); if (!ts->event_wq) { NVT_ERR("ERROR: Cannot create work thread\n"); - goto err_register_drm_notif_failed; + goto err_register_tp_class; } INIT_WORK(&ts->resume_work, nvt_resume_work); @@ -1883,6 +1914,12 @@ static int32_t nvt_ts_probe(struct i2c_client *client, const struct i2c_device_i err_pm_workqueue: destroy_workqueue(ts->event_wq); +#ifdef NVT_TOUCH_COUNT_DUMP +err_register_tp_class: + device_destroy(ts->nvt_tp_class, 0x62); + class_destroy(ts->nvt_tp_class); + ts->nvt_tp_class = NULL; +#endif #if defined(CONFIG_DRM) err_register_drm_notif_failed: @@ -1893,7 +1930,6 @@ static int32_t nvt_ts_probe(struct i2c_client *client, const struct i2c_device_i #if (NVT_TOUCH_PROC || NVT_TOUCH_EXT_PROC || NVT_TOUCH_MP) err_init_NVT_ts: #endif - pm_qos_remove_request(&ts->pm_qos_req); free_irq(client->irq, ts); #if BOOT_UPDATE_FIRMWARE err_create_nvt_fwu_wq_failed: @@ -1931,6 +1967,17 @@ static int32_t nvt_ts_remove(struct i2c_client *client) NVT_ERR("Error occurred while unregistering drm_notifier.\n"); #elif defined(CONFIG_HAS_EARLYSUSPEND) unregister_early_suspend(&ts->early_suspend); +#endif +#ifdef NVT_TOUCH_COUNT_DUMP + if (ts->dump_click_count && !ts->current_clicknum_file) { + kfree(ts->current_clicknum_file); + ts->current_clicknum_file = NULL; + } + sysfs_remove_file(&ts->nvt_touch_dev->kobj, + &dev_attr_touch_suspend_notify.attr); + device_destroy(ts->nvt_tp_class, 0x62); + class_destroy(ts->nvt_tp_class); + ts->nvt_tp_class = NULL; #endif destroy_workqueue(ts->event_wq); @@ -1942,8 +1989,6 @@ static int32_t nvt_ts_remove(struct i2c_client *client) NVT_LOG("Removing driver...\n"); - pm_qos_remove_request(&ts->pm_qos_req); - free_irq(client->irq, ts); input_unregister_device(ts->input_dev); i2c_set_clientdata(client, NULL); @@ -2116,6 +2161,11 @@ static int drm_notifier_callback(struct notifier_block *self, unsigned long even } flush_workqueue(ts->event_wq); nvt_ts_suspend(&ts->client->dev); +#ifdef NVT_TOUCH_COUNT_DUMP + sysfs_notify(&ts->nvt_touch_dev->kobj, NULL, + "touch_suspend_notify"); +#endif + } else if (*blank == DRM_BLANK_UNBLANK) { if (ts->gesture_enabled) { gpio_direction_output(ts->reset_tddi, 0); @@ -2135,6 +2185,10 @@ static int drm_notifier_callback(struct notifier_block *self, unsigned long even } flush_workqueue(ts->event_wq); queue_work(ts->event_wq, &ts->resume_work); +#ifdef NVT_TOUCH_COUNT_DUMP + sysfs_notify(&ts->nvt_touch_dev->kobj, NULL, + "touch_suspend_notify"); +#endif } } @@ -2194,6 +2248,13 @@ static void nvt_ts_late_resume(struct early_suspend *h) } #endif +#if 0 +static const struct dev_pm_ops nvt_ts_dev_pm_ops = { + .suspend = nvt_ts_suspend, + .resume = nvt_ts_resume, +}; +#endif + static const struct i2c_device_id nvt_ts_id[] = { { NVT_I2C_NAME, 0 }, { } diff --git a/drivers/input/touchscreen/nt36xxx/nt36xxx.h b/drivers/input/touchscreen/nt36xxx/nt36xxx.h index 6384cc3bf3b2..fffbbe76615c 100644 --- a/drivers/input/touchscreen/nt36xxx/nt36xxx.h +++ b/drivers/input/touchscreen/nt36xxx/nt36xxx.h @@ -1,6 +1,6 @@ /* * Copyright (C) 2010 - 2017 Novatek, Inc. - * Copyright (C) 2018 XiaoMi, Inc. + * Copyright (C) 2019 XiaoMi, Inc. * * $Revision: 21600 $ * $Date: 2018-01-12 15:21:45 +0800 (週五, 12 一月 2018) $ @@ -26,8 +26,6 @@ #include #endif -#include - #include "nt36xxx_mem_map.h" #define PINCTRL_STATE_ACTIVE "pmx_ts_active" @@ -90,12 +88,20 @@ extern const uint16_t gesture_key_array[]; #define NVT_TOUCH_ESD_CHECK_PERIOD 1500 /* ms */ #define NVT_LOCKDOWN_SIZE 8 +#define NVT_TOUCH_COUNT_DUMP +#ifdef NVT_TOUCH_COUNT_DUMP +#define TOUCH_COUNT_FILE_MAXSIZE 50 +#endif + struct nvt_config_info { u8 tp_vendor; u8 tp_color; u8 tp_hw_version; const char *nvt_cfg_name; const char *nvt_limit_name; +#ifdef NVT_TOUCH_COUNT_DUMP + const char *clicknum_file_name; +#endif }; struct nvt_ts_data { @@ -152,6 +158,7 @@ struct nvt_ts_data { int stylus_enabled; int cover_enabled; int grip_enabled; + int dbclick_count; size_t config_array_size; int current_index; bool dev_pm_suspend; @@ -159,7 +166,13 @@ struct nvt_ts_data { struct work_struct resume_work; struct workqueue_struct *event_wq; struct completion dev_pm_suspend_completion; - struct pm_qos_request pm_qos_req; +#ifdef NVT_TOUCH_COUNT_DUMP + struct class *nvt_tp_class; + struct device *nvt_touch_dev; + bool dump_click_count; + char *current_clicknum_file; +#endif + }; struct nvt_mode_switch { diff --git a/drivers/input/touchscreen/nt36xxx/nt36xxx_ext_proc.c b/drivers/input/touchscreen/nt36xxx/nt36xxx_ext_proc.c index eaa9a173f67e..1c7785e55b81 100644 --- a/drivers/input/touchscreen/nt36xxx/nt36xxx_ext_proc.c +++ b/drivers/input/touchscreen/nt36xxx/nt36xxx_ext_proc.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2010 - 2017 Novatek, Inc. - * Copyright (C) 2018 XiaoMi, Inc. + * Copyright (C) 2019 XiaoMi, Inc. * * $Revision: 21600 $ * $Date: 2018-01-12 15:21:45 +0800 (週五, 12 一月 2018) $ @@ -108,7 +108,7 @@ void nvt_change_mode(uint8_t mode) *******************************************************/ uint8_t nvt_get_fw_pipe(void) { - uint8_t buf[8] = {0}; + uint8_t buf[8]= {0}; /*---set xdata index to EVENT BUF ADDR---*/ buf[0] = 0xFF; @@ -909,7 +909,7 @@ int32_t nvt_get_lockdown_info(char *lockdata) *******************************************************/ int32_t nvt_extra_proc_init(void) { - NVT_proc_fw_version_entry = proc_create(NVT_FW_VERSION, 0444, NULL, &nvt_fw_version_fops); + NVT_proc_fw_version_entry = proc_create(NVT_FW_VERSION, 0444, NULL,&nvt_fw_version_fops); if (NVT_proc_fw_version_entry == NULL) { NVT_ERR("create proc/nvt_fw_version Failed!\n"); return -ENOMEM; @@ -917,7 +917,7 @@ int32_t nvt_extra_proc_init(void) NVT_LOG("create proc/nvt_fw_version Succeeded!\n"); } - NVT_proc_baseline_entry = proc_create(NVT_BASELINE, 0444, NULL, &nvt_baseline_fops); + NVT_proc_baseline_entry = proc_create(NVT_BASELINE, 0444, NULL,&nvt_baseline_fops); if (NVT_proc_baseline_entry == NULL) { NVT_ERR("create proc/nvt_baseline Failed!\n"); return -ENOMEM; @@ -925,7 +925,7 @@ int32_t nvt_extra_proc_init(void) NVT_LOG("create proc/nvt_baseline Succeeded!\n"); } - NVT_proc_raw_entry = proc_create(NVT_RAW, 0444, NULL, &nvt_raw_fops); + NVT_proc_raw_entry = proc_create(NVT_RAW, 0444, NULL,&nvt_raw_fops); if (NVT_proc_raw_entry == NULL) { NVT_ERR("create proc/nvt_raw Failed!\n"); return -ENOMEM; @@ -933,7 +933,7 @@ int32_t nvt_extra_proc_init(void) NVT_LOG("create proc/nvt_raw Succeeded!\n"); } - NVT_proc_diff_entry = proc_create(NVT_DIFF, 0444, NULL, &nvt_diff_fops); + NVT_proc_diff_entry = proc_create(NVT_DIFF, 0444, NULL,&nvt_diff_fops); if (NVT_proc_diff_entry == NULL) { NVT_ERR("create proc/nvt_diff Failed!\n"); return -ENOMEM; diff --git a/drivers/input/touchscreen/nt36xxx/nt36xxx_fw_update.c b/drivers/input/touchscreen/nt36xxx/nt36xxx_fw_update.c index 811f28ac8bae..0f77554be0a0 100644 --- a/drivers/input/touchscreen/nt36xxx/nt36xxx_fw_update.c +++ b/drivers/input/touchscreen/nt36xxx/nt36xxx_fw_update.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2010 - 2017 Novatek, Inc. - * Copyright (C) 2018 XiaoMi, Inc. + * Copyright (C) 2019 XiaoMi, Inc. * * $Revision: 21600 $ * $Date: 2018-01-12 15:21:45 +0800 (週五, 12 一月 2018) $ @@ -32,7 +32,7 @@ #define SIZE_64KB 65536 #define BLOCK_64KB_NUM 4 -const struct firmware *fw_entry = NULL; +const struct firmware *fw_entry; /******************************************************* Description: @@ -85,7 +85,7 @@ void update_firmware_release(void) if (fw_entry) { release_firmware(fw_entry); } - fw_entry = NULL; + fw_entry=NULL; } /******************************************************* @@ -211,7 +211,7 @@ int32_t Check_CheckSum(void) if (Resume_PD()) { NVT_ERR("Resume PD error!!\n"); - return -1; + return -EPERM; } fw_bin_size = fw_entry->size; @@ -636,11 +636,11 @@ int32_t Write_Flash(void) } } if (fw_entry->size - Flash_Address >= 256) - tmpvalue = (Flash_Address >> 16) + ((Flash_Address >> 8) & 0xFF) + (Flash_Address & 0xFF) + 0x00 + (255); + tmpvalue=(Flash_Address >> 16) + ((Flash_Address >> 8) & 0xFF) + (Flash_Address & 0xFF) + 0x00 + (255); else - tmpvalue = (Flash_Address >> 16) + ((Flash_Address >> 8) & 0xFF) + (Flash_Address & 0xFF) + 0x00 + (fw_entry->size - Flash_Address - 1); + tmpvalue=(Flash_Address >> 16) + ((Flash_Address >> 8) & 0xFF) + (Flash_Address & 0xFF) + 0x00 + (fw_entry->size - Flash_Address - 1); - for (k = 0; k < min(fw_entry->size - Flash_Address,(size_t)256); k++) + for (k = 0;k < min(fw_entry->size - Flash_Address,(size_t)256); k++) tmpvalue += fw_entry->data[Flash_Address + k]; tmpvalue = 255 - tmpvalue + 1; diff --git a/drivers/input/touchscreen/nt36xxx/nt36xxx_mem_map.h b/drivers/input/touchscreen/nt36xxx/nt36xxx_mem_map.h index 175f1d4715c7..c717e0958998 100644 --- a/drivers/input/touchscreen/nt36xxx/nt36xxx_mem_map.h +++ b/drivers/input/touchscreen/nt36xxx/nt36xxx_mem_map.h @@ -1,6 +1,6 @@ /* * Copyright (C) 2010 - 2017 Novatek, Inc. - * Copyright (C) 2018 XiaoMi, Inc. + * Copyright (C) 2019 XiaoMi, Inc. * * $Revision: 20544 $ * $Date: 2017-12-20 11:08:15 +0800 (週三, 20 十二月 2017) $ diff --git a/drivers/input/touchscreen/nt36xxx/nt36xxx_mp_ctrlram.c b/drivers/input/touchscreen/nt36xxx/nt36xxx_mp_ctrlram.c index 1c3d7c87527c..faa343818819 100644 --- a/drivers/input/touchscreen/nt36xxx/nt36xxx_mp_ctrlram.c +++ b/drivers/input/touchscreen/nt36xxx/nt36xxx_mp_ctrlram.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2010 - 2017 Novatek, Inc. - * Copyright (C) 2018 XiaoMi, Inc. + * Copyright (C) 2019 XiaoMi, Inc. * * $Revision: 21288 $ * $Date: 2018-01-05 11:38:47 +0800 (週五, 05 一月 2018) $ diff --git a/drivers/input/touchscreen/nt36xxx/nt36xxx_mp_ctrlram.h b/drivers/input/touchscreen/nt36xxx/nt36xxx_mp_ctrlram.h index c3972ad3f003..45c8a8c9fe72 100644 --- a/drivers/input/touchscreen/nt36xxx/nt36xxx_mp_ctrlram.h +++ b/drivers/input/touchscreen/nt36xxx/nt36xxx_mp_ctrlram.h @@ -1,6 +1,6 @@ /* * Copyright (C) 2010 - 2017 Novatek, Inc. - * Copyright (C) 2018 XiaoMi, Inc. + * Copyright (C) 2019 XiaoMi, Inc. * * $Revision: 28342 $ * $Date: 2018-05-18 10:47:10 +0800 (Fri, 18 May 2018) $