/* * Cypress APA trackpad with I2C interface * * Author: Dudley Du <dudl@cypress.com> * * Copyright (C) 2014-2015 Cypress Semiconductor, Inc. * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of this archive for * more details.
*/
/* The offset only valid for retrieve PWC and panel scan commands */ #define GEN5_RESP_DATA_STRUCTURE_OFFSET 10 #define GEN5_PWC_DATA_ELEMENT_SIZE_MASK 0x07
struct cyapa_pip_touch_record { /* * Bit 7 - 3: reserved * Bit 2 - 0: touch type; * 0 : standard finger; * 1 : proximity (Start supported in Gen5 TP). * 2 : finger hover (defined, but not used yet.) * 3 - 15 : reserved.
*/
u8 touch_type;
/* * Bit 7: indicates touch liftoff status. * 0 : touch is currently on the panel. * 1 : touch record indicates a liftoff. * Bit 6 - 5: indicates an event associated with this touch instance * 0 : no event * 1 : touchdown * 2 : significant displacement (> active distance) * 3 : liftoff (record reports last known coordinates) * Bit 4 - 0: An arbitrary ID tag associated with a finger * to allow tracking a touch as it moves around the panel.
*/
u8 touch_tip_event_id;
/* Bit 7 - 0 of X-axis coordinate of the touch in pixel. */
u8 x_lo;
/* Bit 15 - 8 of X-axis coordinate of the touch in pixel. */
u8 x_hi;
/* Bit 7 - 0 of Y-axis coordinate of the touch in pixel. */
u8 y_lo;
/* Bit 15 - 8 of Y-axis coordinate of the touch in pixel. */
u8 y_hi;
/* * The meaning of this value is different when touch_type is different. * For standard finger type: * Touch intensity in counts, pressure value. * For proximity type (Start supported in Gen5 TP): * The distance, in surface units, between the contact and * the surface.
**/
u8 z;
/* * The length of the major axis of the ellipse of contact between * the finger and the panel (ABS_MT_TOUCH_MAJOR).
*/
u8 major_axis_len;
/* * The length of the minor axis of the ellipse of contact between * the finger and the panel (ABS_MT_TOUCH_MINOR).
*/
u8 minor_axis_len;
/* * The length of the major axis of the approaching tool. * (ABS_MT_WIDTH_MAJOR)
*/
u8 major_tool_len;
/* * The length of the minor axis of the approaching tool. * (ABS_MT_WIDTH_MINOR)
*/
u8 minor_tool_len;
/* * The angle between the panel vertical axis and * the major axis of the contact ellipse. This value is an 8-bit * signed integer. The range is -127 to +127 (corresponding to * -90 degree and +90 degree respectively). * The positive direction is clockwise from the vertical axis. * If the ellipse of contact degenerates into a circle, * orientation is reported as 0.
*/
u8 orientation;
} __packed;
struct cyapa_tsg_bin_image_data_record {
u8 flash_array_id;
__be16 row_number; /* The number of bytes of flash data contained in this record. */
__be16 record_len; /* The flash program data. */
u8 record_data[CYAPA_TSG_FW_ROW_SIZE];
} __packed;
struct pip_bl_packet_start {
u8 sop; /* Start of packet, must be 01h */
u8 cmd_code;
__le16 data_length; /* Size of data parameter start from data[0] */
} __packed;
struct pip_bl_packet_end {
__le16 crc;
u8 eop; /* End of packet, must be 17h */
} __packed;
struct pip_bl_cmd_head {
__le16 addr; /* Output report register address, must be 0004h */ /* Size of packet not including output report register address */
__le16 length;
u8 report_id; /* Bootloader output report id, must be 40h */
u8 rsvd; /* Reserved, must be 0 */ struct pip_bl_packet_start packet_start;
u8 data[]; /* Command data variable based on commands */
} __packed;
struct tsg_bl_metadata_row_params {
__le16 size;
__le16 maximum_size;
__le32 app_start;
__le16 app_len;
__le16 app_crc;
__le32 app_entry;
__le32 upgrade_start;
__le16 upgrade_len;
__le16 entry_row_crc;
u8 padding[36]; /* Padding data must be 0 */
__le16 metadata_crc; /* CRC starts at offset of 60 */
} __packed;
/* Bootload program and verify row command data structure */ struct tsg_bl_flash_row_head {
u8 flash_array_id;
__le16 flash_row_id;
u8 flash_data[];
} __packed;
struct pip_app_cmd_head {
__le16 addr; /* Output report register address, must be 0004h */ /* Size of packet not including output report register address */
__le16 length;
u8 report_id; /* Application output report id, must be 2Fh */
u8 rsvd; /* Reserved, must be 0 */ /* * Bit 7: reserved, must be 0. * Bit 6-0: command code.
*/
u8 cmd_code;
u8 parameter_data[]; /* Parameter data variable based on cmd_code */
} __packed;
/* Indicates the pip->pm_stage is not valid. */
mutex_lock(&pip->pm_stage_lock);
pip->pm_stage = CYAPA_PM_DEACTIVE;
mutex_unlock(&pip->pm_stage_lock);
}
/* * This function is aimed to dump all not read data in Gen5 trackpad * before send any command, otherwise, the interrupt line will be blocked.
*/ int cyapa_empty_pip_output_data(struct cyapa *cyapa,
u8 *buf, int *len, cb_sort func)
{ struct input_dev *input = cyapa->input; struct cyapa_pip_cmd_states *pip = &cyapa->cmd_states.pip; enum cyapa_pm_stage pm_stage = cyapa_get_pip_pm_state(cyapa); int length; int report_count; int empty_count; int buf_len; int error;
report_count = 8; /* max 7 pending data before command response data */
empty_count = 0; do { /* * Depending on testing in cyapa driver, there are max 5 "02 00" * packets between two valid buffered data report in firmware. * So in order to dump all buffered data out and * make interrupt line release for reassert again, * we must set the empty_count check value bigger than 5 to * make it work. Otherwise, in some situation, * the interrupt line may unable to reactive again, * which will cause trackpad device unable to * report data any more. * for example, it may happen in EFT and ESD testing.
*/ if (empty_count > 5) return 0;
staticbool cyapa_sort_pip_application_launch_data(struct cyapa *cyapa,
u8 *buf, int len)
{ if (buf == NULL || len < PIP_RESP_LENGTH_SIZE) returnfalse;
/* * After reset or power on, trackpad device always sets to 0x00 0x00 * to indicate a reset or power on event.
*/ if (buf[0] == 0 && buf[1] == 0) returntrue;
returnfalse;
}
staticbool cyapa_sort_gen5_hid_descriptor_data(struct cyapa *cyapa,
u8 *buf, int len)
{ int resp_len; int max_output_len;
/* Check hid descriptor. */ if (len != PIP_HID_DESCRIPTOR_SIZE) returnfalse;
staticint gen5_idle_state_parse(struct cyapa *cyapa)
{
u8 resp_data[PIP_HID_DESCRIPTOR_SIZE]; int max_output_len; int length;
u8 cmd[2]; int ret; int error;
/* * Dump all buffered data firstly for the situation * when the trackpad is just power on the cyapa go here.
*/
cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
memset(resp_data, 0, sizeof(resp_data));
ret = cyapa_i2c_pip_read(cyapa, resp_data, 3); if (ret != 3) return ret < 0 ? ret : -EIO;
length = get_unaligned_le16(&resp_data[PIP_RESP_LENGTH_OFFSET]); if (length == PIP_RESP_LENGTH_SIZE) { /* Normal state of Gen5 with no data to response */
cyapa->gen = CYAPA_GEN5;
staticint gen5_cmd_resp_header_parse(struct cyapa *cyapa, u8 *reg_data)
{ struct cyapa_pip_cmd_states *pip = &cyapa->cmd_states.pip; int length; int ret;
/* * Must read report data through out, * otherwise Gen5 trackpad cannot response next command * or report any touch or button data.
*/
length = get_unaligned_le16(®_data[PIP_RESP_LENGTH_OFFSET]);
ret = cyapa_i2c_pip_read(cyapa, pip->empty_buf, length); if (ret != length) return ret < 0 ? ret : -EIO;
if (cyapa->gen == CYAPA_GEN5) { /* * Must read the content (e.g.: report description and so on) * from trackpad device throughout. Otherwise, * Gen5 trackpad cannot response to next command or * report any touch or button data later.
*/
cyapa_empty_pip_output_data(cyapa, NULL, NULL, NULL);
/* APP_INTEGRITY row is always the last row block */
data = image_records[records_num - 1].record_data;
memcpy(cmd_data->metadata_raw_parameter, data,
CYAPA_TSG_FLASH_MAP_METADATA_SIZE);
/* Verify the firmware image not miss-used for Gen5 and Gen6. */ if (cyapa_pip_fw_head_check(cyapa,
(struct cyapa_tsg_bin_image_head *)fw->data)) {
dev_err(dev, "%s: firmware image not match TP device.\n",
__func__); return -EINVAL;
}
/* * The last flash row 0x01ff has been written through bl_initiate * command, so DO NOT write flash 0x01ff to trackpad device.
*/ for (i = 0; i < (flash_records_count - 1); i++) {
error = cyapa_pip_write_fw_block(cyapa, &image_records[i]); if (error) {
dev_err(dev, "%s: Gen5 FW update aborted: %d\n",
__func__, error); return error;
}
}
if (cyapa->state != CYAPA_STATE_GEN5_APP) return 0;
cyapa_set_pip_pm_state(cyapa, pm_stage);
if (PIP_DEV_GET_PWR_STATE(cyapa) == UNINIT_PWR_MODE) { /* * Assume TP in deep sleep mode when driver is loaded, * avoid driver unload and reload command IO issue caused by TP * has been set into deep sleep mode when unloading.
*/
PIP_DEV_SET_PWR_STATE(cyapa, PWR_MODE_OFF);
}
if (PIP_DEV_UNINIT_SLEEP_TIME(cyapa) &&
PIP_DEV_GET_PWR_STATE(cyapa) != PWR_MODE_OFF) if (cyapa_gen5_get_interval_time(cyapa,
GEN5_PARAMETER_LP_INTRVL_ID,
&cyapa->dev_sleep_time) != 0)
PIP_DEV_SET_SLEEP_TIME(cyapa, UNINIT_SLEEP_TIME);
if (PIP_DEV_GET_PWR_STATE(cyapa) == power_mode) { if (power_mode == PWR_MODE_OFF ||
power_mode == PWR_MODE_FULL_ACTIVE ||
power_mode == PWR_MODE_BTN_ONLY ||
PIP_DEV_GET_SLEEP_TIME(cyapa) == sleep_time) { /* Has in correct power mode state, early return. */ goto out;
}
}
if (power_mode == PWR_MODE_OFF) {
error = cyapa_pip_deep_sleep(cyapa, PIP_DEEP_SLEEP_STATE_OFF); if (error) {
dev_err(dev, "enter deep sleep fail: %d\n", error); goto out;
}
/* * When trackpad in power off mode, it cannot change to other power * state directly, must be wake up from sleep firstly, then * continue to do next power sate change.
*/ if (PIP_DEV_GET_PWR_STATE(cyapa) == PWR_MODE_OFF) {
error = cyapa_pip_deep_sleep(cyapa, PIP_DEEP_SLEEP_STATE_ON); if (error) {
dev_err(dev, "deep sleep wake fail: %d\n", error); goto out;
}
}
if (power_mode == PWR_MODE_FULL_ACTIVE) {
error = cyapa_gen5_change_power_state(cyapa,
GEN5_POWER_STATE_ACTIVE); if (error) {
dev_err(dev, "change to active fail: %d\n", error); goto out;
}
PIP_DEV_SET_PWR_STATE(cyapa, PWR_MODE_FULL_ACTIVE);
} elseif (power_mode == PWR_MODE_BTN_ONLY) {
error = cyapa_gen5_change_power_state(cyapa,
GEN5_POWER_STATE_BTN_ONLY); if (error) {
dev_err(dev, "fail to button only mode: %d\n", error); goto out;
}
PIP_DEV_SET_PWR_STATE(cyapa, PWR_MODE_BTN_ONLY);
} else { /* * Continue to change power mode even failed to set * interval time, it won't affect the power mode change. * except the sleep interval time is not correct.
*/ if (PIP_DEV_UNINIT_SLEEP_TIME(cyapa) ||
sleep_time != PIP_DEV_GET_SLEEP_TIME(cyapa)) if (cyapa_gen5_set_interval_time(cyapa,
GEN5_PARAMETER_LP_INTRVL_ID,
sleep_time) == 0)
PIP_DEV_SET_SLEEP_TIME(cyapa, sleep_time);
if (sleep_time <= GEN5_POWER_READY_MAX_INTRVL_TIME)
power_state = GEN5_POWER_STATE_READY; else
power_state = GEN5_POWER_STATE_IDLE;
error = cyapa_gen5_change_power_state(cyapa, power_state); if (error) {
dev_err(dev, "set power state to 0x%02x failed: %d\n",
power_state, error); goto out;
}
/* * Disable pip report for a little time, firmware will * re-enable it automatically. It's used to fix the issue * that trackpad unable to report signal to wake system up * in the special situation that system is in suspending, and * at the same time, user touch trackpad to wake system up. * This function can avoid the data to be buffered when system * is suspending which may cause interrupt line unable to be * asserted again.
*/ if (pm_stage == CYAPA_PM_SUSPEND)
cyapa_gen5_disable_pip_report(cyapa);
switch (data_size) { case 1:
value = buf[0]; break; case 2: if (big_endian)
value = get_unaligned_be16(buf); else
value = get_unaligned_le16(buf); break; case 4: if (big_endian)
value = get_unaligned_be32(buf); else
value = get_unaligned_le32(buf); break; default: /* Should not happen, just as default case here. */
value = 0; break;
}
if (!unsigned_type)
value = twos_complement_to_s32(value, data_size * 8);
/* * Read all the global mutual or self idac data or mutual or self local PWC * data based on the @idac_data_type. * If the input value of @data_size is 0, then means read global mutual or * self idac data. For read global mutual idac data, @idac_max, @idac_min and * @idac_ave are in order used to return the max value of global mutual idac * data, the min value of global mutual idac and the average value of the * global mutual idac data. For read global self idac data, @idac_max is used * to return the global self cap idac data in Rx direction, @idac_min is used * to return the global self cap idac data in Tx direction. @idac_ave is not * used. * If the input value of @data_size is not 0, than means read the mutual or * self local PWC data. The @idac_max, @idac_min and @idac_ave are used to * return the max, min and average value of the mutual or self local PWC data. * Note, in order to read mutual local PWC data, must read invoke this function * to read the mutual global idac data firstly to set the correct Rx number * value, otherwise, the read mutual idac and PWC data may not correct.
*/ staticint cyapa_gen5_read_idac_data(struct cyapa *cyapa,
u8 cmd_code, u8 idac_data_type, int *data_size, int *idac_max, int *idac_min, int *idac_ave)
{ struct pip_app_cmd_head *cmd_head;
u8 cmd[12];
u8 resp_data[256]; int resp_len; int read_len; int value;
u16 offset; int read_elements; bool read_global_idac; int sum, count, max_element_cnt; int tmp_max, tmp_min, tmp_ave, tmp_sum, tmp_count; int electrodes_rx, electrodes_tx; int i; int error;
/* Read mutual global idac or local mutual/self PWC data. */
offset += read_len; for (i = 10; i < (read_len + GEN5_RESP_DATA_STRUCTURE_OFFSET);
i += *data_size) {
value = cyapa_parse_structure_data(resp_data[9],
&resp_data[i], *data_size);
*idac_min = min(value, *idac_min);
*idac_max = max(value, *idac_max);
if (idac_data_type == GEN5_RETRIEVE_MUTUAL_PWC_DATA &&
tmp_count < cyapa->aligned_electrodes_rx &&
read_global_idac) { /* * The value gap between global and local mutual * idac data must bigger than 50%. * Normally, global value bigger than 50, * local values less than 10.
*/ if (!tmp_ave || value > tmp_ave / 2) {
tmp_min = min(value, tmp_min);
tmp_max = max(value, tmp_max);
tmp_sum += value;
tmp_count++;
tmp_ave = tmp_sum / tmp_count;
}
}
sum += value;
count++;
if (count >= max_element_cnt) goto out;
}
} while (true);
out:
*idac_ave = count ? (sum / count) : 0;
if (read_global_idac &&
idac_data_type == GEN5_RETRIEVE_MUTUAL_PWC_DATA) { if (tmp_count == 0) return 0;
staticint cyapa_gen5_read_mutual_idac_data(struct cyapa *cyapa, int *gidac_mutual_max, int *gidac_mutual_min, int *gidac_mutual_ave, int *lidac_mutual_max, int *lidac_mutual_min, int *lidac_mutual_ave)
{ int data_size; int error;
staticint cyapa_gen5_read_self_idac_data(struct cyapa *cyapa, int *gidac_self_rx, int *gidac_self_tx, int *lidac_self_max, int *lidac_self_min, int *lidac_self_ave)
{ int data_size; int error;
staticint cyapa_gen5_read_panel_scan_raw_data(struct cyapa *cyapa,
u8 cmd_code, u8 raw_data_type, int raw_data_max_num, int *raw_data_max, int *raw_data_min, int *raw_data_ave,
u8 *buffer)
{ struct pip_app_cmd_head *app_cmd_head; struct gen5_retrieve_panel_scan_data *panel_sacn_data;
u8 cmd[12];
u8 resp_data[256]; /* Max bytes can transfer one time. */ int resp_len; int read_elements; int read_len;
u16 offset;
s32 value; int sum, count; int data_size;
s32 *intp; int i; int error;
static ssize_t cyapa_gen5_show_baseline(struct device *dev, struct device_attribute *attr, char *buf)
{ struct cyapa *cyapa = dev_get_drvdata(dev); int gidac_mutual_max, gidac_mutual_min, gidac_mutual_ave; int lidac_mutual_max, lidac_mutual_min, lidac_mutual_ave; int gidac_self_rx, gidac_self_tx; int lidac_self_max, lidac_self_min, lidac_self_ave; int raw_cap_mutual_max, raw_cap_mutual_min, raw_cap_mutual_ave; int raw_cap_self_max, raw_cap_self_min, raw_cap_self_ave; int mutual_diffdata_max, mutual_diffdata_min, mutual_diffdata_ave; int self_diffdata_max, self_diffdata_min, self_diffdata_ave; int mutual_baseline_max, mutual_baseline_min, mutual_baseline_ave; int self_baseline_max, self_baseline_min, self_baseline_ave; int error, resume_error; int size;
bool cyapa_pip_sort_system_info_data(struct cyapa *cyapa,
u8 *buf, int len)
{ /* Check the report id and command code */ if (VALID_CMD_RESP_HEADER(buf, 0x02)) returntrue;
returnfalse;
}
staticint cyapa_gen5_bl_query_data(struct cyapa *cyapa)
{
u8 resp_data[PIP_BL_APP_INFO_RESP_LENGTH]; int resp_len; int error;
switch (cyapa->state) { case CYAPA_STATE_GEN5_BL:
error = cyapa_pip_bl_exit(cyapa); if (error) { /* Try to update trackpad product information. */
cyapa_gen5_bl_query_data(cyapa); goto out;
}
cyapa->state = CYAPA_STATE_GEN5_APP;
fallthrough;
case CYAPA_STATE_GEN5_APP: /* * If trackpad device in deep sleep mode, * the app command will fail. * So always try to reset trackpad device to full active when * the device state is required.
*/
error = cyapa_gen5_set_power_mode(cyapa,
PWR_MODE_FULL_ACTIVE, 0, CYAPA_PM_ACTIVE); if (error)
dev_warn(dev, "%s: failed to set power active mode.\n",
__func__);
/* By default, the trackpad proximity function is enabled. */ if (cyapa->platform_ver >= 2) {
error = cyapa_pip_set_proximity(cyapa, true); if (error)
dev_warn(dev, "%s: failed to enable proximity.\n",
__func__);
}
/* Get trackpad product information. */
error = cyapa_gen5_get_query_data(cyapa); if (error) goto out; /* Only support product ID starting with CYTRA */ if (memcmp(cyapa->product_id, product_id,
strlen(product_id)) != 0) {
dev_err(dev, "%s: unknown product ID (%s)\n",
__func__, cyapa->product_id);
error = -EINVAL;
} break; default:
error = -EINVAL;
}
out: return error;
}
/* * Return false, do not continue process * Return true, continue process.
*/ bool cyapa_pip_irq_cmd_handler(struct cyapa *cyapa)
{ struct cyapa_pip_cmd_states *pip = &cyapa->cmd_states.pip; int length;
if (atomic_read(&pip->cmd_issued)) { /* Polling command response data. */ if (pip->is_irq_mode == false) returnfalse;
/* * Read out all none command response data. * these output data may caused by user put finger on * trackpad when host waiting the command response.
*/
cyapa_i2c_pip_read(cyapa, pip->irq_cmd_buf,
PIP_RESP_LENGTH_SIZE);
length = get_unaligned_le16(pip->irq_cmd_buf);
length = (length <= PIP_RESP_LENGTH_SIZE) ?
PIP_RESP_LENGTH_SIZE : length; if (length > PIP_RESP_LENGTH_SIZE)
cyapa_i2c_pip_read(cyapa,
pip->irq_cmd_buf, length); if (!(pip->resp_sort_func &&
pip->resp_sort_func(cyapa,
pip->irq_cmd_buf, length))) { /* * Cover the Gen5 V1 firmware issue. * The issue is no interrupt would be asserted from * trackpad device to host for the command response * ready event. Because when there was a finger touch * on trackpad device, and the firmware output queue * won't be empty (always with touch report data), so * the interrupt signal won't be asserted again until * the output queue was previous emptied. * This issue would happen in the scenario that * user always has his/her fingers touched on the * trackpad device during system booting/rebooting.
*/
length = 0; if (pip->resp_len)
length = *pip->resp_len;
cyapa_empty_pip_output_data(cyapa,
pip->resp_data,
&length,
pip->resp_sort_func); if (pip->resp_len && length != 0) {
*pip->resp_len = length;
atomic_dec(&pip->cmd_issued);
complete(&pip->cmd_ready);
} returnfalse;
}
report_len = get_unaligned_le16(
&report_data->report_head[PIP_RESP_LENGTH_OFFSET]); /* Idle, no data for report. */ if (report_len == PIP_RESP_LENGTH_SIZE) return 0;
report_id = report_data->report_head[PIP_RESP_REPORT_ID_OFFSET]; if (report_id == PIP_WAKEUP_EVENT_REPORT_ID &&
report_len == PIP_WAKEUP_EVENT_SIZE) { /* * Device wake event from deep sleep mode for touch. * This interrupt event is used to wake system up. * * Note: * It will introduce about 20~40 ms additional delay * time in receiving for first valid touch report data. * The time is used to execute device runtime resume * process.
*/
pm_runtime_get_sync(dev);
pm_runtime_mark_last_busy(dev);
pm_runtime_put_sync_autosuspend(dev); return 0;
} elseif (report_id != PIP_TOUCH_REPORT_ID &&
report_id != PIP_BTN_REPORT_ID &&
report_id != GEN5_OLD_PUSH_BTN_REPORT_ID &&
report_id != PIP_PUSH_BTN_REPORT_ID &&
report_id != PIP_PROXIMITY_REPORT_ID) { /* Running in BL mode or unknown response data read. */
dev_err(dev, "invalid report_id=0x%02x\n", report_id); return -EINVAL;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.