| /* |
| * syntm12xx.c |
| * Synaptic TM12XX touchscreen driver |
| * |
| * Copyright (C) 2009 Nokia Corporation |
| * Author: Mika Kuoppala <mika.kuoppala@nokia.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. |
| * |
| * This program is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| * more details. |
| * |
| * You should have received a copy of the GNU General Public License along with |
| * this program. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/input.h> |
| #include <linux/platform_device.h> |
| #include <linux/i2c.h> |
| #include <linux/gpio.h> |
| #include <linux/delay.h> |
| #include <linux/interrupt.h> |
| #include <linux/firmware.h> |
| #include <linux/slab.h> |
| |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| #include <linux/earlysuspend.h> |
| #endif |
| |
| #include <plat/syntm12xx.h> |
| |
| #define DRIVER_DESC "Synaptic TM12xx Touchscreen Driver" |
| #define DRIVER_NAME "tm12xx_ts" |
| |
| /* Do validation of firmware image on device */ |
| #define FIRMWARE_VERIFY |
| |
| /* Number of touch points and input devices supported */ |
| #define MAX_TOUCH_POINTS 6 |
| |
| #define REG_PDT_PROPERTIES 0xef |
| #define REG_PAGE_SELECT 0xff |
| |
| #define FUNC_DEVICE_CONTROL 0x01 |
| #define FUNC_BIST 0x08 |
| #define FUNC_2D 0x11 |
| #define FUNC_BUTTONS 0x19 |
| #define FUNC_TIMER 0x32 |
| #define FUNC_FLASH 0x34 |
| #define FUNC_PROXIMITY 0x40 |
| |
| #define MAX_FUNC_DESCS 7 |
| |
| /* Device Control Functionality */ |
| #define DEVICE_CONTROL_DATA_STATUS 0x00 |
| #define DEVICE_CONTROL_DATA_INTR_STATUS 0x01 |
| |
| #define DEVICE_CONTROL_STATUS_SCODE_MASK 0x0f |
| #define STATUS_CODE_NO_ERROR 0x00 |
| #define STATUS_CODE_RESET 0x01 |
| #define STATUS_CODE_INVALID_CONF 0x02 |
| #define STATUS_CODE_DEVICE_FAILURE 0x03 |
| |
| #define DEVICE_CONTROL_CTRL 0 |
| #define DEVICE_CONTROL_CONFIGURED (1 << 7) |
| #define DEVICE_CONTROL_SLEEP_NORMAL 0x00 |
| #define DEVICE_CONTROL_SLEEP_SENSOR 0x01 |
| #define DEVICE_CONTROL_INTR_ENABLE 1 |
| #define INTR_FLASH (1 << 0) |
| #define INTR_STATUS (1 << 1) |
| #define INTR_BIST (1 << 2) |
| #define INTR_2D (1 << 3) |
| #define INTR_BUTTON (1 << 4) |
| #define INTR_UADC (1 << 5) |
| #define INTR_ALL 0x3f |
| |
| #define DEVICE_CONTROL_STATUS_FLASH_PROG (1 << 6) |
| #define DEVICE_CONTROL_STATUS_UNCONFIGURED (1 << 7) |
| |
| #define DEVICE_CONTROL_COMMAND 0x00 |
| #define DEVICE_COMMAND_RESET (1 << 0) |
| #define DEVICE_COMMAND_SHUTDOWN (1 << 1) |
| |
| #define DEVICE_CONTROL_QUERY_MANID 0 |
| #define DEVICE_CONTROL_QUERY_PROD_PROPERTIES 1 |
| #define DEVICE_CONTROL_QUERY_PROD_FAMILY 2 |
| #define DEVICE_CONTROL_QUERY_FW_VER 3 |
| #define DEVICE_CONTROL_QUERY_PROD_ID 11 |
| #define DEVICE_CONTROL_QUERY_PROD_ID_LAST 20 |
| #define PRODUCT_ID_LEN (DEVICE_CONTROL_QUERY_PROD_ID_LAST - \ |
| DEVICE_CONTROL_QUERY_PROD_ID + 1) |
| |
| |
| /* Capasitive Buttons */ |
| |
| #define MAX_BUTTONS 31 |
| |
| #define BUTTON_QUERY_QUERY0 0 |
| #define BUTTON_QUERY_BUTTON_COUNT 1 |
| #define BUTTON_QUERY_BUTTON_MASK 0x1f |
| |
| #define BUTTON_CONFIGURABLE (1 << 0) |
| |
| /* 2D Functionality */ |
| |
| #define TOUCH_QUERY_NUM_SENSORS 0 /* Zero based */ |
| #define TOUCH_QUERY_LEN 6 |
| |
| #define TOUCH_CONTROL_REPORT_MODE 0 |
| #define TOUCH_CONTROL_SENSOR_MAX_X 6 |
| #define TOUCH_CONTROL_SENSOR_MAX_Y 8 |
| #define TOUCH_CONTROL_SENSOR_MAPPING 10 |
| |
| /* This is for now fixed for 2 touch points */ |
| #define TOUCH_DATA_LEN (1 + (MAX_TOUCH_POINTS * 5)) |
| |
| #define FINGER_STATE_NOT_PRESENT 0 |
| #define FINGER_STATE_ACCURATE 1 |
| #define FINGER_STATE_INACCURATE 2 |
| #define FINGER_STATE_RESERVED 3 |
| |
| /* Flashing */ |
| #define FLASH_MAX_SIZE (16*1024) |
| #define FW_MAX_NAME_SIZE 31 |
| |
| #define FLASH_QUERY_BOOTLOADER_ID_0 0 |
| #define FLASH_QUERY_BOOTLOADER_ID_1 1 |
| #define FLASH_QUERY_PROPERTIES 2 |
| #define FLASH_QUERY_BLOCK_SIZE_0 3 |
| #define FLASH_QUERY_BLOCK_SIZE_1 4 |
| #define FLASH_QUERY_FW_BLOCK_COUNT_0 5 |
| #define FLASH_QUERY_FW_BLOCK_COUNT_1 6 |
| #define FLASH_QUERY_CONF_BLOCK_COUNT_0 7 |
| #define FLASH_QUERY_CONF_BLOCK_COUNT_1 8 |
| |
| /* Flash Properties */ |
| #define FLASH_PROPERTY_REGMAP_VERSION (1 << 0) |
| |
| /* Commands for FLASH_DATA_COMMAND */ |
| #define FLASH_CMD_IDLE 0x00 |
| #define FLASH_CMD_FW_CRC_BLOCK 0x01 |
| #define FLASH_CMD_FW_WRITE_BLOCK 0x02 |
| #define FLASH_CMD_ERASE_ALL 0x03 |
| #define FLASH_CMD_CONF_READ_BLOCK 0x05 |
| #define FLASH_CMD_CONF_WRITE_BLOCK 0x06 |
| #define FLASH_CMD_CONF_ERASE_BLOCK 0x07 |
| #define FLASH_CMD_PROGRAM_ENABLE 0x0f |
| |
| #define FLASH_ERROR_SUCCESS 0 |
| #define FLASH_ERROR_RESERVED 1 |
| #define FLASH_ERROR_NOT_ENABLED 2 |
| #define FLASH_ERROR_INVALID_BLOCK 3 |
| #define FLASH_ERROR_BLOCK_NOT_ERASED 4 |
| #define FLASH_ERROR_ERASE_KEY 5 |
| #define FLASH_ERROR_UNKNOWN 6 |
| #define FLASH_ERROR_RESET 7 |
| #define FLASH_ERROR_COUNT 8 |
| |
| #define DEVICE_STATUS_NO_ERROR 0 |
| #define DEVICE_STATUS_RESET_OCCURRED 1 |
| #define DEVICE_STATUS_INVALID_CONFIG 2 |
| #define DEVICE_STATUS_DEVICE_FAILURE 3 |
| #define DEVICE_STATUS_CFG_CRC_FAILURE 4 |
| #define DEVICE_STATUS_FW_CRC_FAILURE 5 |
| #define DEVICE_STATUS_CRC_IN_PROGRESS 6 |
| #define DEVICE_STATUS_UNKNOWN 7 |
| #define DEVICE_STATUS_COUNT 8 |
| |
| /* Selftest */ |
| #define BIST_QUERY_LIMIT_REG_COUNT 0 |
| #define BIST_DATA_TEST_NUMBER_CTRL 0 |
| #define BIST_DATA_OVERALL_RESULT 1 |
| #define BIST_DATA_TEST_RESULT 2 |
| #define BIST_CONTROL_COMMAND 0 |
| |
| static const char *const device_status_str[DEVICE_STATUS_COUNT] = { |
| "no error", |
| "reset occurred", |
| "invalid configuration", |
| "device failure", |
| "configuration crc failure", |
| "firmware crc failure", |
| "crc in progress", |
| "unknown", |
| }; |
| |
| static const char *const flash_error_str[FLASH_ERROR_COUNT] = { |
| "success", |
| "reserved", |
| "programming not enabled", |
| "invalid block number", |
| "block not erased", |
| "erase key incorrect", |
| "unknown", |
| "device reset", |
| }; |
| |
| /* Offsets within firmware image */ |
| #define FW_FILE_CHECKSUM 0x0000 |
| #define FW_VERSION 0x0007 |
| #define FW_FW_SIZE 0x0008 |
| #define FW_CONFIG_SIZE 0x000C |
| #define FW_PRODUCT_ID 0x0010 |
| #define FW_PRODUCT_INFO_0 0x001E |
| #define FW_PRODUCT_INFO_1 0x001F |
| #define FW_FW_DATA 0x0100 |
| |
| /* How many times we try to re-initialize chip in row */ |
| #define MAX_FAILED_INITS 4 |
| |
| /* How much data we can put into single write block */ |
| #define MAX_I2C_WRITE_BLOCK_SIZE 32 |
| |
| #define DFLAG_VERBOSE (1 << 0) |
| #define DFLAG_I2C_DUMP (1 << 1) |
| |
| struct syn; |
| |
| struct func_desc { |
| void (*intr_handler)(struct syn *sd, u8 bits); |
| u8 num; |
| u8 version; |
| u8 query; |
| u8 command; |
| u8 control; |
| u8 data; |
| u8 exists; |
| |
| u8 intr_start_bit; |
| u8 intr_sources; |
| }; |
| |
| struct touch_sensor_caps { |
| unsigned is_configurable:1; |
| unsigned has_gestures:1; |
| unsigned has_abs_mode:1; |
| unsigned has_rel_mode:1; |
| unsigned finger_count:4; |
| unsigned x_electrodes:5; |
| unsigned y_electrodes:5; |
| unsigned max_electrodes:5; |
| unsigned abs_data_size:2; |
| |
| /* XXX These are not yet in use |
| unsigned has_pinch:1; |
| unsigned has_press:1; |
| unsigned has_flick:1; |
| unsigned has_early_tap:1; |
| unsigned has_double_tap:1; |
| unsigned had_tap_and_hold:1; |
| unsigned has_single_tap:1; |
| unsigned has_anchored_finger:1; |
| unsigned has_palm_detect:1; |
| */ |
| |
| u16 max_x; |
| u16 max_y; |
| }; |
| |
| struct button_caps { |
| u8 button_count; |
| }; |
| |
| struct flash_caps { |
| u16 bootloader_id; |
| u16 block_size; |
| u16 fw_block_count; |
| u16 conf_block_count; |
| u8 properties; |
| }; |
| |
| struct fw_image { |
| u32 file_checksum; |
| u8 version; |
| u8 product_id[PRODUCT_ID_LEN + 1]; |
| u8 product_info_0; |
| u8 product_info_1; |
| const u8 *fw_data; |
| u32 fw_size; |
| const u8 *config_data; |
| u32 config_size; |
| u32 config_checksum; |
| }; |
| |
| struct touch_data { |
| u16 x; |
| u16 y; |
| u8 wx; |
| u8 wy; |
| u8 z; |
| u8 finger_state; |
| }; |
| |
| struct touch_point { |
| struct input_dev *idev; |
| struct touch_data cur_td; |
| struct touch_data prev_td; |
| int num; |
| }; |
| |
| struct bist_test_result { |
| u8 failed; /* == 0, if all passed */ |
| u8 result; /* specific error for failed test */ |
| }; |
| |
| struct syn { |
| struct mutex lock; |
| struct i2c_client *client; |
| struct workqueue_struct *wq; |
| struct work_struct isr_work; |
| |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| struct early_suspend early_suspend; |
| #endif |
| |
| struct func_desc *control; |
| u8 device_control_ctrl; /* saved state */ |
| |
| struct func_desc *touch; |
| struct touch_point tp[MAX_TOUCH_POINTS]; |
| struct touch_sensor_caps touch_caps; |
| |
| struct func_desc *buttons; |
| struct button_caps button_caps; |
| u32 button_state; |
| |
| struct func_desc func_desc[MAX_FUNC_DESCS]; |
| unsigned func_desc_num; |
| unsigned interrupt_sources; |
| |
| struct func_desc *flash; |
| struct flash_caps flash_caps; |
| struct fw_image fw_image; |
| const struct firmware *fw_entry; |
| |
| struct func_desc *bist; |
| struct func_desc *timer; |
| struct func_desc *prox; |
| |
| int gpio_intr; |
| int func_descs_valid; |
| |
| int failed_inits; |
| u32 debug_flag; |
| |
| unsigned long ts_intr; |
| unsigned long ts_work; |
| unsigned long ts_done; |
| |
| unsigned long t_wakeup_min; |
| unsigned long t_wakeup_max; |
| unsigned long t_wakeup_c; |
| |
| unsigned long t_work_min; |
| unsigned long t_work_max; |
| unsigned long t_work_c; |
| |
| unsigned long t_wakeup; |
| unsigned long t_work; |
| |
| unsigned long t_count; |
| unsigned long f_measure; |
| |
| int virtualized; |
| int suspend_mode; |
| }; |
| #ifdef CONFIG_PM |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| static void syn_ts_early_suspend(struct early_suspend *handler); |
| static void syn_ts_late_resume(struct early_suspend *handler); |
| #endif |
| #endif |
| |
| static int syn_initialize(struct syn *sd); |
| static int syn_reset_device(struct syn *sd); |
| static int syn_read_func_descs(struct syn *sd); |
| |
| static int syn_write_block(struct syn *sd, int reg, const u8 *data, int len) |
| { |
| unsigned char wb[MAX_I2C_WRITE_BLOCK_SIZE + 1]; |
| struct i2c_msg msg; |
| int r; |
| int i; |
| |
| if (len < 1 || |
| len > MAX_I2C_WRITE_BLOCK_SIZE) { |
| dev_info(&sd->client->dev, "too long syn_write_block len %d\n", |
| len); |
| return -EIO; |
| } |
| |
| wb[0] = reg & 0xff; |
| |
| for (i = 0; i < len; i++) |
| wb[i + 1] = data[i]; |
| |
| msg.addr = sd->client->addr; |
| msg.flags = 0; |
| msg.len = len + 1; |
| msg.buf = wb; |
| |
| r = i2c_transfer(sd->client->adapter, &msg, 1); |
| |
| if (sd->debug_flag & DFLAG_I2C_DUMP) { |
| if (r == 1) { |
| for (i = 0; i < len; i++) |
| dev_info(&sd->client->dev, |
| "bw 0x%02x[%d]: 0x%02x\n", |
| reg + i, i, data[i]); |
| } |
| } |
| |
| if (r == 1) |
| return 0; |
| |
| return r; |
| } |
| |
| static int syn_read_block(struct syn *sd, int reg, u8 *data, int len) |
| { |
| unsigned char wb[1]; |
| struct i2c_msg msg[2]; |
| int r; |
| |
| wb[0] = reg & 0xff; |
| |
| msg[0].addr = sd->client->addr; |
| msg[0].flags = 0; |
| msg[0].len = 1; |
| msg[0].buf = wb; |
| |
| msg[1].addr = msg[0].addr; |
| msg[1].flags = I2C_M_RD; |
| msg[1].len = len; |
| msg[1].buf = data; |
| |
| r = i2c_transfer(sd->client->adapter, msg, 2); |
| |
| if (sd->debug_flag & DFLAG_I2C_DUMP) { |
| if (r == 2) { |
| int i; |
| |
| for (i = 0; i < len; i++) |
| dev_info(&sd->client->dev, |
| "br 0x%02x[%d]: 0x%02x\n", |
| reg + i, i, data[i]); |
| } |
| } |
| |
| if (r == 2) |
| return len; |
| |
| return r; |
| } |
| |
| static int syn_read_u8(struct syn *sd, int reg) |
| { |
| unsigned char b[1] = {0}; |
| int r; |
| |
| r = syn_read_block(sd, reg, b, 1); |
| if (r == 1) |
| return (int)b[0]; |
| |
| return r; |
| } |
| |
| static int syn_write_u8(struct syn *sd, u8 reg, u8 value) |
| { |
| return syn_write_block(sd, reg, &value, 1); |
| } |
| |
| static int syn_read_u16(struct syn *sd, int reg) |
| { |
| int r; |
| u8 data[2] = {0}; |
| |
| r = syn_read_block(sd, reg, data, 2); |
| if (r < 0) |
| return r; |
| |
| return (int)data[0] | ((int)(data[1]) << 8); |
| } |
| |
| static int syn_write_u16(struct syn *sd, int reg, u16 value) |
| { |
| u8 data[2]; |
| |
| data[1] = (value & 0xFF00) >> 8; |
| data[0] = value & 0x00FF; |
| |
| return syn_write_block(sd, reg, data, 2); |
| } |
| |
| static int syn_control_query_read(struct syn *sd, int reg) |
| { |
| return syn_read_u8(sd, sd->control->query + reg); |
| } |
| |
| static int syn_control_data_read(struct syn *sd, int reg) |
| { |
| return syn_read_u8(sd, sd->control->data + reg); |
| } |
| |
| static const char *syn_device_status_str(u8 dev_status) |
| { |
| if (dev_status >= DEVICE_STATUS_COUNT) |
| return device_status_str[DEVICE_STATUS_UNKNOWN]; |
| |
| return device_status_str[dev_status]; |
| } |
| |
| static int syn_get_nosleep(struct syn *sd) |
| { |
| int r; |
| |
| r = syn_read_u8(sd, sd->control->control + DEVICE_CONTROL_CTRL); |
| if (r < 0) |
| return r; |
| |
| return (r & 0x04) >> 2; |
| } |
| |
| static int syn_set_nosleep(struct syn *sd, int val) |
| { |
| int r; |
| |
| r = syn_read_u8(sd, sd->control->control + DEVICE_CONTROL_CTRL); |
| if (r < 0) |
| return r; |
| |
| val = (r & (~0x04)) | ((!!val) << 2); |
| |
| r = syn_write_u8(sd, sd->control->control + DEVICE_CONTROL_CTRL, |
| val); |
| |
| return r; |
| } |
| |
| static int syn_get_proximity_state(struct syn *sd) |
| { |
| int r; |
| |
| if (!sd->prox) |
| return -ENODEV; |
| |
| r = syn_read_u8(sd, sd->prox->control); |
| if (r < 0) |
| return r; |
| |
| return (r & (1 << 7)) == 0; |
| } |
| |
| static int syn_set_proximity_state(struct syn *sd, int val) |
| { |
| int r; |
| |
| if (!sd->prox) |
| return -ENODEV; |
| |
| r = syn_read_u8(sd, sd->prox->control); |
| if (r < 0) |
| return r; |
| |
| /* It is called 'inhibit proximity' in register */ |
| val = (r & (~0x80)) | ((!val) << 7); |
| |
| r = syn_write_u8(sd, sd->prox->control, |
| val); |
| if (r < 0) |
| return r; |
| |
| return r; |
| } |
| |
| static void syn_report_device_status(struct syn *sd, u8 dev_status) |
| { |
| struct device *dev = &sd->client->dev; |
| |
| if (dev_status & (1 << 7)) |
| dev_info(dev, "device is unconfigured\n"); |
| |
| if (dev_status & (1 << 6)) |
| dev_info(dev, "device is in flashing mode\n"); |
| |
| dev_info(dev, "device status: 0x%x, %s\n", dev_status, |
| syn_device_status_str(dev_status & 0x0f)); |
| } |
| |
| static void syn_device_change_state(struct syn *sd, u8 dev_status) |
| { |
| int r; |
| |
| /* Unconfigured or reset, we initialize*/ |
| if ((dev_status & (1 << 7)) || |
| (dev_status & 0x0f) == DEVICE_STATUS_RESET_OCCURRED) { |
| r = syn_initialize(sd); |
| if (r) |
| dev_err(&sd->client->dev, |
| "error %d in syn_initialize\n", r); |
| } |
| } |
| |
| static void syn_recalculate_latency_data(struct syn *sd) |
| { |
| if (sd->ts_intr <= sd->ts_work && sd->ts_work < sd->ts_done) { |
| sd->t_wakeup = sd->ts_work - sd->ts_intr; |
| do_div(sd->t_wakeup, 1000); |
| sd->t_work = sd->ts_done - sd->ts_work; |
| do_div(sd->t_work, 1000); |
| |
| if (sd->t_wakeup > sd->t_wakeup_max) |
| sd->t_wakeup_max = sd->t_wakeup; |
| |
| if (sd->t_wakeup < sd->t_wakeup_min) |
| sd->t_wakeup_min = sd->t_wakeup; |
| |
| if (sd->t_work > sd->t_work_max) |
| sd->t_work_max = sd->t_work; |
| |
| if (sd->t_work < sd->t_work_min) |
| sd->t_work_min = sd->t_work; |
| |
| if ((sd->t_work_c + sd->t_work) < sd->t_work_c || |
| (sd->t_wakeup_c + sd->t_wakeup) < sd->t_wakeup_c) { |
| |
| /* Wrap */ |
| sd->t_work_c = sd->t_work; |
| sd->t_wakeup_c = sd->t_wakeup; |
| sd->t_count = 0; |
| } else { |
| sd->t_work_c += sd->t_work; |
| sd->t_wakeup_c += sd->t_wakeup; |
| sd->t_count++; |
| } |
| } else { |
| /* Time wrap or something else, discard */ |
| } |
| } |
| |
| static void syn_isr_device_control(struct syn *sd, u8 bits) |
| { |
| int dev_status; |
| |
| dev_status = syn_control_data_read(sd, DEVICE_CONTROL_DATA_STATUS); |
| if (dev_status < 0) { |
| dev_err(&sd->client->dev, "error %d reading device status\n", |
| dev_status); |
| } |
| |
| syn_report_device_status(sd, dev_status); |
| |
| syn_device_change_state(sd, dev_status); |
| } |
| |
| static void syn_isr_flash(struct syn *sd, u8 bits) |
| { |
| int dev_status; |
| |
| dev_info(&sd->client->dev, "flash interrupt\n"); |
| |
| if (!sd->flash) { |
| dev_warn(&sd->client->dev, |
| "flash interrupt without registered functionality\n"); |
| return; |
| } |
| |
| dev_status = syn_control_data_read(sd, DEVICE_CONTROL_DATA_STATUS); |
| syn_report_device_status(sd, dev_status); |
| |
| syn_device_change_state(sd, dev_status); |
| } |
| |
| static void syn_isr_timer(struct syn *sd, u8 bits) |
| { |
| if (!sd->timer) { |
| dev_warn(&sd->client->dev, |
| "flash interrupt without registered functionality\n"); |
| return; |
| } |
| } |
| |
| static void syn_isr_bist(struct syn *sd, u8 bits) |
| { |
| if (!sd->bist) { |
| dev_warn(&sd->client->dev, |
| "bist interrupt without registered functionality\n"); |
| return; |
| } |
| } |
| |
| static void syn_isr_proximity(struct syn *sd, u8 bits) |
| { |
| unsigned char data[6] = {0}; |
| struct input_dev *idev; |
| int r = 0; |
| |
| idev = sd->tp[0].idev; |
| |
| if (!sd->prox) { |
| dev_warn(&sd->client->dev, |
| "spurious proximity interrupty\n"); |
| return; |
| } |
| |
| r = syn_read_block(sd, sd->prox->data, data, 6); |
| if (r < 0) { |
| dev_warn(&sd->client->dev, |
| "error %d reading proximity data\n", r); |
| return; |
| } |
| |
| if (data[0] & 0x01) { |
| input_report_abs(idev, ABS_RZ, data[1]); |
| input_report_abs(idev, ABS_HAT0X, data[2]); /* BH */ |
| input_report_abs(idev, ABS_HAT0Y, data[4]); /* LH */ |
| input_report_abs(idev, ABS_HAT1X, data[3]); /* TH */ |
| input_report_abs(idev, ABS_HAT1Y, data[5]); /* RH */ |
| } else |
| input_report_abs(idev, ABS_RZ, 0); |
| |
| input_sync(idev); |
| } |
| |
| static void syn_touch_parse_one_touch_data(struct syn *sd, struct touch_data *p, |
| u8 finger_state, const u8 *d) |
| { |
| p->finger_state = finger_state; |
| p->x = (u16)(d[2] & 0x0f) | (u16)(d[0] << 4); |
| p->y = ((u16)(d[2] & 0xf0) >> 4) | (u16)(d[1] << 4); |
| p->wx = d[3] & 0x0f; |
| p->wy = (d[3] & 0xf0) >> 4; |
| p->z = d[4]; |
| } |
| |
| static void syn_touch_parse_touch_data(struct syn *sd, const u8 *d) |
| { |
| struct tm12xx_ts_platform_data *pdata = sd->client->dev.platform_data; |
| unsigned i; |
| u8 fc; |
| u8 fs; |
| struct touch_data *p; |
| |
| fc = sd->touch_caps.finger_count; |
| |
| if (fc > MAX_TOUCH_POINTS) |
| dev_err(&sd->client->dev, "error in finger count %d\n", fc); |
| |
| for (i = 0; i < fc; i++) { |
| p = &sd->tp[pdata->controller_num].cur_td; |
| |
| fs = (d[(i >> 2)] >> ((i % 4) << 1)) & 0x03; |
| |
| /* dev_warn(&sd->client->dev, "fs[%d] = %d\n", i, fs); */ |
| |
| syn_touch_parse_one_touch_data(sd, p, fs, |
| d + ((fc >> 2) + 1) + |
| i * 5); |
| |
| } |
| } |
| |
| static int syn_touch_get_data(struct syn *sd) |
| { |
| int r; |
| u8 data[TOUCH_DATA_LEN]; |
| |
| r = syn_read_block(sd, sd->touch->data, data, TOUCH_DATA_LEN); |
| if (r < 0) |
| return r; |
| |
| if (r != TOUCH_DATA_LEN) |
| return -EIO; |
| |
| syn_touch_parse_touch_data(sd, data); |
| |
| return 0; |
| } |
| |
| static void syn_touch_report_data(struct syn *sd, int dev_number) |
| { |
| struct tm12xx_ts_platform_data *pdata = sd->client->dev.platform_data; |
| struct input_dev *idev; |
| struct touch_data *d; |
| struct touch_data *p; |
| |
| idev = sd->tp[dev_number].idev; |
| d = &sd->tp[dev_number].cur_td; |
| p = &sd->tp[dev_number].prev_td; |
| |
| if (memcmp(p, d, sizeof(struct touch_data)) == 0) |
| return; |
| |
| memcpy(p, d, sizeof(struct touch_data)); |
| |
| switch (d->finger_state) { |
| |
| case FINGER_STATE_ACCURATE: |
| input_report_key(idev, BTN_TOUCH, 1); |
| input_report_key(idev, BTN_MODE, 0); |
| break; |
| |
| case FINGER_STATE_INACCURATE: |
| input_report_key(idev, BTN_TOUCH, 1); |
| input_report_key(idev, BTN_MODE, 1); |
| break; |
| |
| case FINGER_STATE_RESERVED: |
| /* Intentional fall thru */ |
| |
| case FINGER_STATE_NOT_PRESENT: |
| input_report_key(idev, BTN_MODE, 0); |
| input_report_key(idev, BTN_TOUCH, 0); |
| goto out; |
| |
| default: |
| dev_err(&sd->client->dev, "unknown finger state 0x%x\n", |
| d->finger_state); |
| goto out; |
| } |
| |
| if (sd->virtualized && pdata->swap_xy && |
| dev_name(&sd->client->dev)[0] != '2') |
| d->x = sd->touch_caps.max_x + d->x; |
| |
| if (pdata->swap_xy) { |
| input_report_abs(idev, ABS_X, d->y); |
| input_report_abs(idev, ABS_Y, d->x); |
| input_report_abs(idev, ABS_TOOL_WIDTH, d->wy); |
| } else { |
| input_report_abs(idev, ABS_X, d->x); |
| input_report_abs(idev, ABS_Y, d->y); |
| input_report_abs(idev, ABS_TOOL_WIDTH, d->wx); |
| } |
| |
| input_report_abs(idev, ABS_Z, d->z); |
| input_report_abs(idev, ABS_VOLUME, d->wx * d->wy); |
| |
| out: |
| input_sync(idev); |
| } |
| |
| static void syn_isr_2d(struct syn *sd, u8 bits) |
| { |
| struct tm12xx_ts_platform_data *pdata = sd->client->dev.platform_data; |
| int r; |
| |
| if (!sd->touch) { |
| dev_warn(&sd->client->dev, |
| "2d interrupt without registered functionality\n"); |
| return; |
| } |
| |
| r = syn_touch_get_data(sd); |
| if (r) { |
| dev_err(&sd->client->dev, "error getting touch data\n"); |
| return; |
| } |
| |
| if (sd->debug_flag & DFLAG_VERBOSE) { |
| dev_info(&sd->client->dev, |
| "%i: state=0x%x, x=%d, y=%d, z=%d, wx=%d, wy=%d\n", |
| pdata->controller_num, |
| sd->tp[pdata->controller_num].cur_td.finger_state, |
| sd->tp[pdata->controller_num].cur_td.x, |
| sd->tp[pdata->controller_num].cur_td.y, |
| sd->tp[pdata->controller_num].cur_td.z, |
| sd->tp[pdata->controller_num].cur_td.wx, |
| sd->tp[pdata->controller_num].cur_td.wy); |
| } |
| |
| syn_touch_report_data(sd, pdata->controller_num); |
| } |
| |
| static int syn_button_report(struct syn *sd, unsigned button_nr, const int val) |
| { |
| struct tm12xx_ts_platform_data *pdata = sd->client->dev.platform_data; |
| unsigned max_buttons; |
| |
| max_buttons = pdata->num_buttons; |
| |
| if (button_nr >= max_buttons) |
| return -EINVAL; |
| |
| input_report_key(sd->tp[0].idev, pdata->button_map[button_nr], val); |
| input_sync(sd->tp[0].idev); |
| |
| return 0; |
| } |
| |
| static void syn_isr_buttons(struct syn *sd, u8 bits) |
| { |
| int data_reg_count = (sd->button_caps.button_count + 7) / 8; |
| int i = 0; |
| int r; |
| u32 state = 0; |
| u32 last_state; |
| |
| if (!sd->buttons) { |
| dev_warn(&sd->client->dev, |
| "button interrupt without registered functionality\n"); |
| return; |
| } |
| |
| if (data_reg_count > MAX_BUTTONS/8 + 1) |
| data_reg_count = MAX_BUTTONS/8 + 1; |
| |
| while (i < data_reg_count) { |
| r = syn_read_u8(sd, sd->buttons->data + i); |
| if (r < 0) { |
| dev_err(&sd->client->dev, |
| "error reading button state\n"); |
| return; |
| } |
| |
| state |= (u32)r << (i * 8); |
| i++; |
| } |
| |
| last_state = sd->button_state; |
| |
| for (i = 0; i < sd->button_caps.button_count; i++) { |
| const u32 mask = (1 << i); |
| if ((state & mask) ^ (last_state & mask)) { |
| r = syn_button_report(sd, i, state & mask ? 1 : 0); |
| if (r) { |
| dev_warn(&sd->client->dev, |
| "error reporting button " |
| "(no mapping for %d ?)\n", i); |
| } |
| } |
| } |
| |
| sd->button_state = state; |
| } |
| |
| static u8 syn_get_status_bits(u8 *int_status, int start, int bits) |
| { |
| const u16 mask = ((1 << bits) - 1) << start; |
| u8 val; |
| |
| val = (*int_status & mask) >> start; |
| |
| *int_status &= ~mask; |
| |
| return val; |
| } |
| |
| static void syn_isr_call_handlers(struct syn *sd, u8 int_status) |
| { |
| int i; |
| u8 bits; |
| |
| for (i = 0; i < sd->func_desc_num; i++) { |
| struct func_desc * const f = &sd->func_desc[i]; |
| |
| if (!int_status) |
| return; |
| |
| bits = syn_get_status_bits(&int_status, f->intr_start_bit, |
| f->intr_sources); |
| |
| if (likely(bits)) { |
| if (likely(f->intr_handler)) |
| f->intr_handler(sd, bits); |
| else { |
| if (printk_ratelimit()) |
| dev_warn(&sd->client->dev, |
| "no intr handler for %d\n", i); |
| } |
| } |
| } |
| |
| if (unlikely(int_status)) |
| dev_warn(&sd->client->dev, |
| "unhandled attentions: 0x%x\n", int_status); |
| } |
| |
| static void syn_clear_device_state(struct syn *sd) |
| { |
| int i; |
| |
| sd->control = NULL; |
| |
| sd->touch = NULL; |
| /* |
| * We don't clear previous exported devices. |
| * If after firmware upgrade there would be different |
| * amount of touchpoints rmmod/insmod cycle is needed. |
| */ |
| memset(&sd->touch_caps, 0, sizeof(struct touch_sensor_caps)); |
| |
| sd->buttons = NULL; |
| memset(&sd->button_caps, 0, sizeof(struct button_caps)); |
| sd->button_state = 0; |
| |
| for (i = 0; i < MAX_FUNC_DESCS; i++) |
| memset(&sd->func_desc[i], 0, sizeof(struct func_desc)); |
| |
| sd->func_desc_num = 0; |
| sd->interrupt_sources = 0; |
| |
| sd->flash = NULL; |
| memset(&sd->flash_caps, 0, sizeof(struct flash_caps)); |
| memset(&sd->fw_image, 0, sizeof(struct fw_image)); |
| |
| sd->bist = NULL; |
| } |
| |
| static void syn_isr_work(struct work_struct *work) |
| { |
| int is; |
| int r; |
| struct syn *sd = container_of(work, struct syn, isr_work); |
| |
| mutex_lock(&sd->lock); |
| |
| if (sd->f_measure) { |
| sd->ts_work = cpu_clock(get_cpu()); |
| put_cpu(); |
| } |
| |
| if (sd->func_descs_valid == 0) { |
| if (sd->failed_inits < MAX_FAILED_INITS) { |
| r = syn_initialize(sd); |
| if (r) { |
| dev_err(&sd->client->dev, |
| "error initializing\n"); |
| goto out; |
| } |
| } |
| } |
| |
| is = syn_control_data_read(sd, DEVICE_CONTROL_DATA_INTR_STATUS); |
| |
| if (unlikely(is < 0)) { |
| dev_err(&sd->client->dev, |
| "unable to read intr status\n"); |
| syn_reset_device(sd); |
| goto out; |
| } |
| |
| if (likely(is)) |
| syn_isr_call_handlers(sd, is); |
| |
| out: |
| if (sd->f_measure) { |
| sd->ts_done = cpu_clock(get_cpu()); |
| put_cpu(); |
| syn_recalculate_latency_data(sd); |
| sd->f_measure = 0; |
| } |
| enable_irq(gpio_to_irq(sd->gpio_intr)); |
| mutex_unlock(&sd->lock); |
| } |
| |
| static irqreturn_t syn_isr(int irq, void *data) |
| { |
| struct syn *sd = data; |
| |
| if (sd->wq != NULL) { |
| int r; |
| r = queue_work(sd->wq, &sd->isr_work); |
| if (r) { |
| if (sd->f_measure == 0) { |
| sd->ts_intr = cpu_clock(get_cpu()); |
| put_cpu(); |
| sd->f_measure = 1; |
| } |
| disable_irq_nosync(gpio_to_irq(sd->gpio_intr)); |
| } |
| |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int syn_set_input_dev_params(struct syn *sd, int dev_number) |
| { |
| struct input_dev *dev; |
| struct tm12xx_ts_platform_data *pdata; |
| int i; |
| |
| if (!sd || dev_number < 0 || dev_number >= MAX_TOUCH_POINTS) |
| return -EINVAL; |
| |
| dev = sd->tp[dev_number].idev; |
| if (!dev) |
| return -ENODEV; |
| |
| pdata = sd->client->dev.platform_data; |
| |
| set_bit(EV_KEY, dev->evbit); |
| set_bit(EV_ABS, dev->evbit); |
| |
| set_bit(BTN_TOUCH, dev->keybit); |
| set_bit(BTN_MODE, dev->keybit); |
| |
| set_bit(ABS_X, dev->absbit); |
| set_bit(ABS_Y, dev->absbit); |
| set_bit(ABS_Z, dev->absbit); |
| set_bit(ABS_VOLUME, dev->absbit); |
| set_bit(ABS_TOOL_WIDTH, dev->absbit); |
| |
| if (dev_number == 0) { |
| set_bit(ABS_RZ, dev->absbit); |
| set_bit(ABS_HAT0X, dev->absbit); |
| set_bit(ABS_HAT0Y, dev->absbit); |
| set_bit(ABS_HAT1X, dev->absbit); |
| set_bit(ABS_HAT1Y, dev->absbit); |
| } |
| |
| if (pdata->repeat) |
| set_bit(EV_REP, dev->evbit); |
| |
| for (i = 0; i < pdata->num_buttons; i++) |
| set_bit(pdata->button_map[i], dev->keybit); |
| |
| if (pdata->swap_xy) { |
| input_set_abs_params(dev, ABS_X, 0, sd->touch_caps.max_y, 0, 0); |
| input_set_abs_params(dev, ABS_Y, 0, sd->touch_caps.max_x, 0, 0); |
| } else { |
| input_set_abs_params(dev, ABS_X, 0, sd->touch_caps.max_x, 0, 0); |
| input_set_abs_params(dev, ABS_Y, 0, sd->touch_caps.max_y, 0, 0); |
| } |
| |
| input_set_abs_params(dev, ABS_Z, 0, 255, 0, 0); |
| input_set_abs_params(dev, ABS_VOLUME, 0, 225, 0, 0); |
| input_set_abs_params(dev, ABS_TOOL_WIDTH, 0, 15, 0, 0); |
| |
| if (dev_number == 0) { |
| input_set_abs_params(dev, ABS_RZ, 0, 255, 0, 0); |
| input_set_abs_params(dev, ABS_HAT0X, 0, 255, 0, 0); |
| input_set_abs_params(dev, ABS_HAT0Y, 0, 255, 0, 0); |
| input_set_abs_params(dev, ABS_HAT1X, 0, 255, 0, 0); |
| input_set_abs_params(dev, ABS_HAT1Y, 0, 255, 0, 0); |
| } |
| |
| dev->dev.parent = &sd->client->dev; |
| |
| return 0; |
| } |
| |
| static int syn_register_input_devices(struct syn *sd, int num_devices) |
| { |
| struct tm12xx_ts_platform_data *pdata; |
| int r; |
| |
| if (!sd || num_devices < 0 || num_devices > MAX_TOUCH_POINTS) |
| return -EINVAL; |
| |
| pdata = sd->client->dev.platform_data; |
| |
| /* After firmware upgrade no need to reregister */ |
| if (sd->tp[0].idev != NULL) { |
| if (sd->debug_flag & DFLAG_VERBOSE) |
| dev_info(&sd->client->dev, |
| "input device already existing\n"); |
| return 0; |
| } |
| |
| sd->tp[pdata->controller_num].idev = input_allocate_device(); |
| if (!sd->tp[pdata->controller_num].idev) { |
| dev_err(&sd->client->dev, |
| "not enough memory for input device %d\n", |
| pdata->controller_num); |
| r = -ENOMEM; |
| goto err_alloc; |
| } |
| |
| r = syn_set_input_dev_params(sd, pdata->controller_num); |
| if (r) { |
| dev_err(&sd->client->dev, |
| "%s: Setting input params for controller \ |
| number %i failed.\n", |
| __func__, pdata->controller_num); |
| goto err_alloc; |
| } |
| |
| if (pdata->idev_name[pdata->controller_num]) |
| sd->tp[pdata->controller_num].idev->name = |
| pdata->idev_name[pdata->controller_num]; |
| else { |
| dev_err(&sd->client->dev, |
| "%s: No input device name for controller \ |
| number %i.\n", __func__, pdata->controller_num); |
| goto err_alloc; |
| } |
| |
| r = input_register_device(sd->tp[pdata->controller_num].idev); |
| if (r) { |
| dev_err(&sd->client->dev, |
| "failed to register input device %d\n", |
| pdata->controller_num); |
| goto err_register; |
| } |
| |
| return 0; |
| |
| err_register: |
| input_unregister_device(sd->tp[pdata->controller_num].idev); |
| err_alloc: |
| if (sd->tp[pdata->controller_num].idev) { |
| input_free_device(sd->tp[pdata->controller_num].idev); |
| sd->tp[pdata->controller_num].idev = NULL; |
| } |
| |
| return r; |
| } |
| |
| static struct func_desc *syn_get_func_desc(struct syn *sd, int func) |
| { |
| int i; |
| |
| for (i = 0; i < sd->func_desc_num; i++) { |
| struct func_desc * const f = &sd->func_desc[i]; |
| |
| if (f->num == func) |
| return f; |
| } |
| |
| return NULL; |
| } |
| |
| static int syn_register_intr_handler(struct syn *sd, int func, |
| void (*h)(struct syn *sd, u8 bits)) |
| { |
| struct func_desc * const f = syn_get_func_desc(sd, func); |
| |
| if (!f) |
| return -EINVAL; |
| |
| if (f->intr_handler == NULL) { |
| f->intr_handler = h; |
| return 0; |
| } |
| |
| return -EINVAL; |
| } |
| |
| /* |
| * Flashing support |
| */ |
| static int syn_flash_query_caps(struct syn *sd) |
| { |
| int r; |
| |
| r = syn_read_u16(sd, sd->flash->query + FLASH_QUERY_BOOTLOADER_ID_0); |
| if (r < 0) |
| return r; |
| |
| sd->flash_caps.bootloader_id = r; |
| |
| r = syn_read_u8(sd, sd->flash->query + FLASH_QUERY_PROPERTIES); |
| if (r < 0) |
| return r; |
| |
| sd->flash_caps.properties = r; |
| |
| r = syn_read_u16(sd, sd->flash->query + FLASH_QUERY_BLOCK_SIZE_0); |
| if (r < 0) |
| return r; |
| |
| sd->flash_caps.block_size = r; |
| |
| r = syn_read_u16(sd, sd->flash->query + FLASH_QUERY_FW_BLOCK_COUNT_0); |
| if (r < 0) |
| return r; |
| |
| sd->flash_caps.fw_block_count = r; |
| |
| r = syn_read_u16(sd, sd->flash->query + FLASH_QUERY_CONF_BLOCK_COUNT_0); |
| if (r < 0) |
| return r; |
| |
| sd->flash_caps.conf_block_count = r; |
| |
| return 0; |
| } |
| |
| static u32 syn_crc_fletcher32(const u16 *data, unsigned int len) |
| { |
| u32 sum1 = 0xffff; |
| u32 sum2 = 0xffff; |
| |
| while (len--) { |
| sum1 += *data++; |
| sum2 += sum1; |
| |
| sum1 = (sum1 & 0xffff) + (sum1 >> 16); |
| sum2 = (sum2 & 0xffff) + (sum2 >> 16); |
| } |
| |
| return sum1 | (sum2 << 16); |
| } |
| |
| static int syn_fw_read_bytes(struct syn *sd, u8 *b, int offset, int len) |
| { |
| if (offset + len > sd->fw_entry->size) { |
| dev_err(&sd->client->dev, |
| "fw_read_bytes overflow on offset %d\n", offset); |
| return -EINVAL; |
| } |
| |
| memcpy(b, sd->fw_entry->data + offset, len); |
| |
| return len; |
| } |
| |
| static int syn_fw_read_u32(struct syn *sd, u32 *d, int offset) |
| { |
| const u8 *l; |
| |
| if (offset + 4 > sd->fw_entry->size) { |
| dev_err(&sd->client->dev, |
| "fw_read_u32 overflow on offset %d\n", offset); |
| |
| return -EINVAL; |
| } |
| |
| l = sd->fw_entry->data + offset; |
| |
| *d = l[0] | (l[1] << 8) | (l[2] << 16) | (l[3] << 24); |
| |
| return 4; |
| } |
| |
| static int syn_request_firmware(struct syn *sd, const char *filename) |
| { |
| int r; |
| |
| r = request_firmware(&sd->fw_entry, filename, &sd->client->dev); |
| |
| return r; |
| } |
| |
| static int syn_check_firmware(struct syn *sd) |
| { |
| u32 calc_file_crc; |
| u32 calc_config_crc; |
| struct fw_image *d = &sd->fw_image; |
| int r; |
| |
| /* |
| * Restrict sizes. |
| * Absolute minimum is zero sized fw data and config |
| * areas. |
| */ |
| |
| if (sd->fw_entry->size > FLASH_MAX_SIZE || |
| sd->fw_entry->size < FW_FW_DATA + 4) { |
| dev_err(&sd->client->dev, "illegal firmware size\n"); |
| return -EINVAL; |
| } |
| |
| r = syn_fw_read_u32(sd, &d->file_checksum, FW_FILE_CHECKSUM); |
| if (r < 0) |
| return r; |
| |
| r = syn_fw_read_bytes(sd, &d->version, FW_VERSION, 1); |
| if (r < 0) |
| return r; |
| |
| r = syn_fw_read_u32(sd, &d->fw_size, FW_FW_SIZE); |
| if (r < 0) |
| return r; |
| |
| r = syn_fw_read_u32(sd, &d->config_size, FW_CONFIG_SIZE); |
| if (r < 0) |
| return r; |
| |
| if ((d->fw_size + d->config_size + FW_FW_DATA) > sd->fw_entry->size) { |
| dev_err(&sd->client->dev, "fw size mismatch\n"); |
| return -EINVAL; |
| } |
| |
| /* These have to be u32 aligned */ |
| if ((d->fw_size & 0x0f) || (d->config_size & 0x0f)) { |
| dev_err(&sd->client->dev, "fw areas not aligned to 4 bytes\n"); |
| return -EINVAL; |
| } |
| |
| r = syn_fw_read_bytes(sd, d->product_id, FW_PRODUCT_ID, |
| PRODUCT_ID_LEN); |
| if (r < 10) |
| return r; |
| |
| r = syn_fw_read_bytes(sd, &d->product_info_0, |
| FW_PRODUCT_INFO_0, 1); |
| if (r < 0) |
| return r; |
| |
| r = syn_fw_read_bytes(sd, &d->product_info_1, |
| FW_PRODUCT_INFO_1, 1); |
| if (r < 0) |
| return r; |
| |
| r = syn_fw_read_u32(sd, &d->config_checksum, |
| d->fw_size + FW_FW_DATA + d->config_size - 4); |
| if (r < 0) |
| return r; |
| |
| d->fw_data = sd->fw_entry->data + FW_FW_DATA; |
| d->config_data = sd->fw_entry->data + FW_FW_DATA + d->fw_size; |
| |
| calc_file_crc = syn_crc_fletcher32((u16 *)(sd->fw_entry->data + 4), |
| (sd->fw_entry->size - 4) >> 1); |
| |
| calc_config_crc = syn_crc_fletcher32((u16 *)(d->config_data), |
| (d->config_size - 4) >> 1); |
| |
| if (calc_file_crc != d->file_checksum) { |
| dev_err(&sd->client->dev, "fw file crc failed\n"); |
| return -EINVAL; |
| } |
| |
| if (calc_config_crc != d->config_checksum) { |
| dev_err(&sd->client->dev, "fw config area crc failed\n"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static const char *syn_flash_error_str(u8 flash_error) |
| { |
| if (flash_error >= FLASH_ERROR_COUNT) |
| return flash_error_str[FLASH_ERROR_UNKNOWN]; |
| |
| return flash_error_str[flash_error]; |
| } |
| |
| static u8 syn_flash_error(int flash_status) |
| { |
| return ((u8)(flash_status) >> 4) & 0x07; |
| } |
| |
| /* |
| * There are two different ways the flash register are organized |
| */ |
| static u8 syn_flash_data_command_offset(struct syn *sd) |
| { |
| if (sd->flash_caps.properties & FLASH_PROPERTY_REGMAP_VERSION) |
| return 2 + sd->flash_caps.block_size; |
| else |
| return 0; |
| } |
| |
| static u8 syn_flash_block_data_offset(struct syn *sd) |
| { |
| if (sd->flash_caps.properties & FLASH_PROPERTY_REGMAP_VERSION) |
| return 2; |
| else |
| return 3; |
| } |
| |
| static u8 syn_flash_block_num_offset(struct syn *sd) |
| { |
| if (sd->flash_caps.properties & FLASH_PROPERTY_REGMAP_VERSION) |
| return 0; |
| else |
| return 1; |
| } |
| |
| static int syn_flash_status(struct syn *sd) |
| { |
| return syn_read_u8(sd, sd->flash->data + |
| syn_flash_data_command_offset(sd)); |
| } |
| |
| static int syn_wait_for_attn(struct syn *sd, unsigned long timeout_usecs, |
| int state) |
| { |
| const unsigned long one_wait = 20; |
| unsigned long waited = 0; |
| int r; |
| |
| do { |
| r = gpio_get_value(sd->gpio_intr); |
| if (r == state) |
| break; |
| |
| udelay(one_wait); |
| waited += one_wait; |
| } while (waited < timeout_usecs); |
| |
| if (waited > 500000 || |
| waited >= timeout_usecs || |
| (sd->debug_flag & DFLAG_VERBOSE)) |
| dev_info(&sd->client->dev, "waited %lu usecs for attn\n", |
| waited); |
| |
| return r == state ? 0 : -1; |
| } |
| |
| static int syn_flash_command(struct syn *sd, u8 flash_command) |
| { |
| int r; |
| int s; |
| |
| if (flash_command & 0xf0) |
| return -EINVAL; |
| |
| r = syn_wait_for_attn(sd, 200 * 1000, 1); |
| if (r) { |
| dev_err(&sd->client->dev, |
| "timeout: attn didn't clear for 200 ms\n"); |
| return r; |
| } |
| |
| r = syn_write_u8(sd, |
| sd->flash->data + syn_flash_data_command_offset(sd), |
| flash_command); |
| |
| if (r) { |
| dev_err(&sd->client->dev, "flash command error %d\n", r); |
| return r; |
| } |
| |
| r = syn_wait_for_attn(sd, 700 * 1000, 0); |
| if (r) { |
| dev_err(&sd->client->dev, |
| "timeout attn didn't assert for 700 ms\n"); |
| } |
| |
| s = syn_control_data_read(sd, DEVICE_CONTROL_DATA_INTR_STATUS); |
| if (s < 0) |
| dev_err(&sd->client->dev, "error reading int status\n"); |
| |
| if (sd->debug_flag & DFLAG_VERBOSE) { |
| if (s != 0x01) |
| dev_info(&sd->client->dev, |
| "wait for attn intr status 0x%x\n", s); |
| } |
| |
| return r == 0 ? 0 : -1; |
| } |
| |
| static int syn_flash_enable(struct syn *sd) |
| { |
| int r; |
| |
| r = syn_control_data_read(sd, DEVICE_CONTROL_DATA_STATUS); |
| if (r < 0) |
| return r; |
| |
| if (r & (1 << 6)) { |
| dev_err(&sd->client->dev, "flashing mode already enabled\n"); |
| return 0; |
| } |
| |
| r = syn_write_u16(sd, sd->flash->data + syn_flash_block_data_offset(sd), |
| sd->flash_caps.bootloader_id); |
| if (r < 0) |
| return r; |
| |
| r = syn_flash_command(sd, FLASH_CMD_PROGRAM_ENABLE); |
| if (r < 0) |
| return r; |
| |
| r = syn_read_func_descs(sd); |
| if (r < 0) |
| return r; |
| |
| r = syn_flash_status(sd); |
| if (r < 0) { |
| dev_info(&sd->client->dev, "flash error in enable %s\n", |
| syn_flash_error_str(syn_flash_error(r))); |
| return r; |
| } |
| |
| if (syn_flash_error(r) != FLASH_ERROR_SUCCESS && |
| syn_flash_error(r) != FLASH_ERROR_RESET) { |
| dev_err(&sd->client->dev, "flash error: %s\n", |
| syn_flash_error_str(syn_flash_error(r))); |
| |
| return -EINVAL; |
| } |
| |
| if (!(r & 0x80)) { |
| dev_err(&sd->client->dev, "flashing not enabled 0x%02x\n", r); |
| return -EINVAL; |
| } |
| |
| return r; |
| } |
| |
| static int syn_flash_write_block_cmd_old(struct syn *sd, const u8 *data, |
| int blocks, u8 flash_command) |
| { |
| int block; |
| int r; |
| const u8 block_num_offset = syn_flash_block_num_offset(sd); |
| const u8 block_data_offset = syn_flash_block_data_offset(sd); |
| |
| for (block = 0; block < blocks; block++) { |
| if (!(block % 10)) |
| dev_info(&sd->client->dev, "0x%x writing block %d\n", |
| flash_command, block); |
| |
| r = syn_write_u16(sd, sd->flash->data + block_num_offset, |
| block); |
| if (r < 0) |
| return r; |
| |
| r = syn_write_block(sd, sd->flash->data + block_data_offset, |
| data + (block * sd->flash_caps.block_size), |
| sd->flash_caps.block_size); |
| if (r < 0) |
| return r; |
| |
| r = syn_flash_command(sd, flash_command); |
| if (r < 0) |
| return r; |
| |
| r = syn_flash_status(sd); |
| if (r < 0) |
| return r; |
| |
| if (r != 0x80) { |
| dev_err(&sd->client->dev, |
| "flash_write error : %s\n", |
| syn_flash_error_str(syn_flash_error(r))); |
| return syn_flash_error(r); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int syn_flash_write_block_cmd_fast(struct syn *sd, const u8 *data, |
| int blocks, u8 flash_command) |
| { |
| const u16 block_size = sd->flash_caps.block_size; |
| u8 *d; |
| u16 block; |
| int r = 0; |
| |
| d = kmalloc(block_size + 3, GFP_KERNEL); |
| if (d == NULL) |
| return -ENOMEM; |
| |
| for (block = 0; block < blocks; block++) { |
| if (!(block % 10)) |
| dev_info(&sd->client->dev, |
| "0x%x fast writing block %d\n", |
| flash_command, block); |
| d[0] = block & 0xff; |
| d[1] = (block & 0xff00) >> 8; |
| memcpy(&d[2], data + (block * block_size), block_size); |
| d[2 + block_size] = flash_command; |
| |
| r = syn_wait_for_attn(sd, 200 * 1000, 1); |
| if (r) { |
| dev_err(&sd->client->dev, |
| "timeout: attn didn't clear for 200 ms\n"); |
| goto out; |
| } |
| |
| r = syn_write_block(sd, sd->flash->data, |
| d, block_size + 3); |
| |
| |
| r = syn_wait_for_attn(sd, 700 * 1000, 0); |
| if (r) { |
| dev_err(&sd->client->dev, |
| "timeout attn didn't assert for 700 ms\n"); |
| goto out; |
| } |
| |
| /* We need to do this read to release ATTN */ |
| syn_control_data_read(sd, DEVICE_CONTROL_DATA_INTR_STATUS); |
| } |
| out: |
| kfree(d); |
| |
| return r; |
| } |
| |
| static int syn_flash_write_block_cmd(struct syn *sd, const u8 *data, |
| int blocks, u8 flash_command) |
| { |
| if (sd->flash_caps.properties & FLASH_PROPERTY_REGMAP_VERSION) |
| return syn_flash_write_block_cmd_fast(sd, data, |
| blocks, flash_command); |
| else |
| return syn_flash_write_block_cmd_old(sd, data, |
| blocks, flash_command); |
| } |
| |
| static int syn_flash_read_config(struct syn *sd, u8 *d) |
| { |
| const u8 flash_command = FLASH_CMD_CONF_READ_BLOCK; |
| const u8 block_num_offset = syn_flash_block_num_offset(sd); |
| const u8 block_data_offset = syn_flash_block_data_offset(sd); |
| const int blocks = sd->fw_image.config_size / sd->flash_caps.block_size; |
| int i; |
| int r; |
| |
| for (i = 0; i < blocks; i++) { |
| if (!(i % 10)) |
| dev_info(&sd->client->dev, |
| "0x%x reading conf block %d\n", |
| flash_command, i); |
| |
| r = syn_write_u16(sd, sd->flash->data + block_num_offset, i); |
| if (r < 0) |
| return r; |
| |
| r = syn_flash_command(sd, flash_command); |
| if (r < 0) |
| return r; |
| |
| r = syn_flash_status(sd); |
| if (r < 0) |
| return r; |
| |
| if (r != 0x80) { |
| dev_err(&sd->client->dev, |
| "flash_read error : %s\n", |
| syn_flash_error_str(syn_flash_error(r))); |
| return syn_flash_error(r); |
| } |
| |
| r = syn_read_block(sd, sd->flash->data + block_data_offset, d, |
| sd->flash_caps.block_size); |
| if (r < 0) |
| return r; |
| |
| d += sd->flash_caps.block_size; |
| } |
| |
| return 0; |
| } |
| |
| static int syn_flash_write_config(struct syn *sd) |
| { |
| int r; |
| const int config_size = sd->fw_image.config_size; |
| const int blocks = config_size / sd->flash_caps.block_size; |
| |
| r = syn_flash_write_block_cmd(sd, sd->fw_image.config_data, blocks, |
| FLASH_CMD_CONF_WRITE_BLOCK); |
| if (r < 0) |
| return r; |
| |
| if (r) { |
| dev_err(&sd->client->dev, |
| "flash write fw error : %s\n", |
| syn_flash_error_str(r)); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| #ifdef FIRMWARE_VERIFY |
| static int syn_flash_validate(struct syn *sd) |
| { |
| int r; |
| const int fw_size = sd->fw_image.fw_size; |
| const int blocks = fw_size / sd->flash_caps.block_size; |
| |
| if (sd->flash_caps.fw_block_count != |
| (fw_size / sd->flash_caps.block_size) || |
| (fw_size % sd->flash_caps.block_size)) { |
| dev_err(&sd->client->dev, |
| "fw size not aligned to block count\n"); |
| return -EINVAL; |
| } |
| |
| r = syn_flash_write_block_cmd(sd, sd->fw_image.fw_data, blocks, |
| FLASH_CMD_FW_CRC_BLOCK); |
| if (r < 0) |
| return r; |
| |
| if (r) { |
| dev_err(&sd->client->dev, |
| "flash validate error : %s\n", |
| syn_flash_error_str(r)); |
| return -EIO; |
| } |
| |
| r = syn_read_u8(sd, sd->flash->data + syn_flash_block_data_offset(sd)); |
| if (r < 0) |
| return r; |
| |
| if (r == 0x00) { |
| dev_info(&sd->client->dev, "flash_validate: image is valid\n"); |
| return 0; |
| } else if (r == 0xff) |
| dev_info(&sd->client->dev, |
| "flash_validate: image is invalid\n"); |
| else |
| dev_info(&sd->client->dev, |
| "flash_validate: image status unknown: 0x%02x\n", r); |
| |
| return -1; |
| } |
| |
| static int syn_flash_compare_config(struct syn *sd) |
| { |
| u32 cfg_crc_c; |
| u32 cfg_crc; |
| u32 cfg_crc_r; |
| u8 *cfg_data_r; |
| int r; |
| |
| cfg_data_r = kmalloc(sd->fw_image.config_size, GFP_KERNEL); |
| if (cfg_data_r == NULL) |
| return -ENOMEM; |
| |
| r = syn_flash_read_config(sd, cfg_data_r); |
| if (r < 0) { |
| kfree(cfg_data_r); |
| return r; |
| } |
| |
| dev_info(&sd->client->dev, "Flash read config done\n"); |
| |
| cfg_crc = sd->fw_image.config_checksum; |
| cfg_crc_c = syn_crc_fletcher32((u16 *)(sd->fw_image.config_data), |
| (sd->fw_image.config_size - 4) >> 1); |
| cfg_crc_r = syn_crc_fletcher32((u16 *)(cfg_data_r), |
| (sd->fw_image.config_size - 4) >> 1); |
| |
| kfree(cfg_data_r); |
| cfg_data_r = NULL; |
| |
| dev_info(&sd->client->dev, "calculated fw image crc 0x%x\n", cfg_crc_c); |
| dev_info(&sd->client->dev, "read fw image crc 0x%x\n", cfg_crc); |
| dev_info(&sd->client->dev, "calculated cfg read crc 0x%x\n", cfg_crc_r); |
| |
| if (cfg_crc != cfg_crc_c) { |
| dev_err(&sd->client->dev, |
| "fw crc differs from calculated\n"); |
| return -EINVAL; |
| } |
| |
| if (cfg_crc != cfg_crc_r) { |
| dev_err(&sd->client->dev, |
| "fw crc differs from read from device\n"); |
| return -EINVAL; |
| } |
| |
| if (cfg_crc_c != cfg_crc_r) { |
| dev_err(&sd->client->dev, |
| "fw calculated crc differs from read from device\n"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| #else /* FIRMWARE_VERIFY */ |
| static int syn_flash_compare_config(struct syn *sd) |
| { |
| return 0; |
| } |
| |
| static int syn_flash_validate(struct syn *sd) |
| { |
| return 0; |
| } |
| #endif |
| |
| static int syn_flash_erase_all(struct syn *sd) |
| { |
| int r; |
| |
| r = syn_write_u16(sd, sd->flash->data + syn_flash_block_data_offset(sd), |
| sd->flash_caps.bootloader_id); |
| |
| r = syn_flash_command(sd, FLASH_CMD_ERASE_ALL); |
| if (r < 0) |
| return r; |
| |
| r = syn_flash_status(sd); |
| if (r < 0) |
| return r; |
| |
| if (r != 0x80) { |
| dev_err(&sd->client->dev, |
| "flash erase_all error : %d %s\n", syn_flash_error(r), |
| syn_flash_error_str(syn_flash_error(r))); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static int syn_flash_write_fw(struct syn *sd) |
| { |
| int r; |
| const int fw_size = sd->fw_image.fw_size; |
| const int blocks = fw_size / sd->flash_caps.block_size; |
| |
| r = syn_flash_write_block_cmd(sd, sd->fw_image.fw_data, blocks, |
| FLASH_CMD_FW_WRITE_BLOCK); |
| if (r < 0) |
| return r; |
| |
| r = syn_flash_status(sd); |
| if (r < 0) |
| return r; |
| |
| if (r != 0x80) { |
| dev_err(&sd->client->dev, |
| "flash write fw : %d %s\n", syn_flash_error(r), |
| syn_flash_error_str(syn_flash_error(r))); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static int syn_flash_firmware(struct syn *sd) |
| { |
| int r; |
| int flash_status; |
| |
| /* Restrict interrupts during flashing */ |
| r = syn_write_u8(sd, sd->control->control + DEVICE_CONTROL_INTR_ENABLE, |
| INTR_FLASH | INTR_STATUS); |
| |
| r = syn_flash_enable(sd); |
| if (r < 0) |
| goto flash_out; |
| |
| r = syn_flash_validate(sd); |
| if (r < 0) |
| goto flash_out; |
| |
| r = syn_flash_erase_all(sd); |
| if (r < 0) |
| goto flash_out; |
| |
| r = syn_flash_write_fw(sd); |
| if (r < 0) |
| goto flash_out; |
| |
| r = syn_flash_write_config(sd); |
| if (r < 0) |
| goto flash_out; |
| |
| r = syn_flash_compare_config(sd); |
| if (r < 0) |
| goto flash_out; |
| |
| flash_out: |
| flash_status = syn_flash_status(sd); |
| dev_info(&sd->client->dev, |
| "flash status: %x %s\n", flash_status, |
| syn_flash_error_str(syn_flash_error(flash_status))); |
| |
| /* |
| * We reset and interrupt handler should notice that |
| * everything has changed and reread the page descriptor table. |
| */ |
| syn_reset_device(sd); |
| |
| return syn_flash_error(flash_status) == FLASH_ERROR_SUCCESS ? 0 : -1; |
| } |
| |
| static ssize_t syn_show_attr_flash(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct syn *sd = platform_get_drvdata(pdev); |
| const ssize_t size = PAGE_SIZE; |
| ssize_t l = 0; |
| int r; |
| |
| if (!sd->flash) { |
| l += snprintf(buf + l, size - l, "N/A\n"); |
| return l; |
| } |
| |
| mutex_lock(&sd->lock); |
| |
| r = syn_control_data_read(sd, DEVICE_CONTROL_DATA_STATUS); |
| |
| mutex_unlock(&sd->lock); |
| |
| if (r < 0) |
| dev_err(&sd->client->dev, "read error %d\n", r); |
| else |
| l += snprintf(buf + l, size - l, "%d\n", |
| (r & (1 << 6)) ? 1 : 0); |
| |
| return l; |
| } |
| |
| static ssize_t syn_store_attr_flash(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| const struct platform_device *pdev = to_platform_device(dev); |
| struct syn *sd = platform_get_drvdata(pdev); |
| int r; |
| char name[FW_MAX_NAME_SIZE + 1]; |
| |
| if (count > FW_MAX_NAME_SIZE || count == 0) { |
| dev_err(&sd->client->dev, "firmware name check failure\n"); |
| return 0; |
| } |
| |
| memcpy(name, buf, count); |
| name[count] = 0; |
| |
| mutex_lock(&sd->lock); |
| |
| if (name[count - 1] == '\n') |
| name[count - 1] = 0; |
| |
| if (sd->fw_entry) { |
| dev_err(&sd->client->dev, "firmware already in memory\n"); |
| goto firmware_out; |
| } |
| |
| r = syn_request_firmware(sd, name); |
| if (r < 0) { |
| dev_err(&sd->client->dev, "firmware not found\n"); |
| goto store_out; |
| } |
| |
| memset(&sd->fw_image, 0, sizeof(struct fw_image)); |
| |
| r = syn_check_firmware(sd); |
| if (r != 0) { |
| dev_err(&sd->client->dev, |
| "consistency check of firmware failed\n"); |
| goto firmware_out; |
| } |
| |
| dev_info(&sd->client->dev, "firmware consistency check ok\n"); |
| |
| r = syn_flash_firmware(sd); |
| if (r != 0) { |
| dev_err(&sd->client->dev, |
| "flashing of firmware failed\n"); |
| goto firmware_out; |
| } |
| |
| dev_info(&sd->client->dev, "flashing done with success\n"); |
| |
| firmware_out: |
| memset(&sd->fw_image, 0, sizeof(struct fw_image)); |
| release_firmware(sd->fw_entry); |
| sd->fw_entry = NULL; |
| |
| store_out: |
| mutex_unlock(&sd->lock); |
| |
| return count; |
| } |
| |
| static ssize_t syn_show_attr_product_id(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct syn *sd = platform_get_drvdata(pdev); |
| const ssize_t size = PAGE_SIZE; |
| u8 product_id[PRODUCT_ID_LEN + 1]; |
| ssize_t l = 0; |
| int r; |
| |
| if (!sd->flash) { |
| l += snprintf(buf + l, size - l, "N/A\n"); |
| return l; |
| } |
| |
| mutex_lock(&sd->lock); |
| |
| r = syn_read_block(sd, sd->control->query + |
| DEVICE_CONTROL_QUERY_PROD_ID, |
| product_id, PRODUCT_ID_LEN); |
| |
| mutex_unlock(&sd->lock); |
| |
| product_id[PRODUCT_ID_LEN] = 0; |
| |
| if (r < 0) |
| dev_warn(&sd->client->dev, |
| "error %d reading product id\n", r); |
| else |
| l += snprintf(buf + l, size - l, "%s\n", |
| product_id); |
| |
| return l; |
| } |
| |
| static ssize_t syn_show_attr_product_family(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct syn *sd = platform_get_drvdata(pdev); |
| const ssize_t size = PAGE_SIZE; |
| ssize_t l = 0; |
| int r; |
| |
| if (!sd->flash) { |
| l += snprintf(buf + l, size - l, "N/A\n"); |
| return l; |
| } |
| |
| mutex_lock(&sd->lock); |
| |
| r = syn_control_query_read(sd, |
| DEVICE_CONTROL_QUERY_PROD_FAMILY); |
| |
| mutex_unlock(&sd->lock); |
| |
| if (r < 0) |
| dev_warn(&sd->client->dev, |
| "error %d reading product family\n", r); |
| else |
| l += snprintf(buf + l, size - l, "%d\n", |
| r); |
| |
| return l; |
| } |
| |
| static ssize_t syn_show_attr_firmware_version(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct syn *sd = platform_get_drvdata(pdev); |
| const ssize_t size = PAGE_SIZE; |
| ssize_t l = 0; |
| int r; |
| |
| if (!sd->flash) { |
| l += snprintf(buf + l, size - l, "N/A\n"); |
| return l; |
| } |
| |
| mutex_lock(&sd->lock); |
| |
| r = syn_control_query_read(sd, |
| DEVICE_CONTROL_QUERY_FW_VER); |
| |
| mutex_unlock(&sd->lock); |
| |
| if (r < 0) |
| dev_warn(&sd->client->dev, |
| "error %d reading fw version\n", r); |
| else |
| l += snprintf(buf + l, size - l, "%d\n", |
| r); |
| |
| return l; |
| } |
| |
| /* |
| * BIST (selftest) support |
| */ |
| |
| static int syn_bist_run_test(struct syn *sd, struct bist_test_result *tr, |
| u8 test) |
| { |
| int r; |
| u8 intr_mask = INTR_ALL; |
| |
| if (!sd || !tr || !sd->bist) |
| return -EINVAL; |
| |
| mutex_lock(&sd->lock); |
| |
| r = syn_read_u8(sd, sd->control->control + |
| DEVICE_CONTROL_INTR_ENABLE); |
| if (r < 0) |
| goto out; |
| |
| intr_mask = r & 0xFF; |
| |
| /* Restrict interrupt sources*/ |
| r = syn_write_u8(sd, sd->control->control + DEVICE_CONTROL_INTR_ENABLE, |
| INTR_BIST | INTR_STATUS); |
| |
| r = syn_write_u8(sd, sd->bist->data + BIST_DATA_TEST_NUMBER_CTRL, |
| test); |
| if (r < 0) |
| goto out; |
| |
| r = syn_write_u8(sd, sd->bist->command + BIST_CONTROL_COMMAND, |
| 0x01); |
| if (r < 0) |
| goto out; |
| |
| r = syn_wait_for_attn(sd, 5 * 1000 * 1000, 0); |
| if (r < 0) { |
| dev_err(&sd->client->dev, "timeout running bist test\n"); |
| goto out; |
| } |
| |
| r = syn_control_data_read(sd, DEVICE_CONTROL_DATA_INTR_STATUS); |
| if (r < 0) |
| dev_err(&sd->client->dev, "error reading int status\n"); |
| |
| r = syn_read_u8(sd, sd->bist->command + BIST_CONTROL_COMMAND); |
| if (r < 0) |
| goto out; |
| |
| if (r & (1 << 0)) { |
| r = -EBUSY; |
| goto out; |
| } |
| |
| r = syn_read_u8(sd, sd->bist->data + BIST_DATA_OVERALL_RESULT); |
| if (r < 0) |
| goto out; |
| |
| tr->failed = r; |
| |
| r = syn_read_u8(sd, sd->bist->data + BIST_DATA_TEST_RESULT); |
| if (r < 0) |
| goto out; |
| |
| tr->result = r; |
| |
| out: |
| r = syn_write_u8(sd, sd->control->control + DEVICE_CONTROL_INTR_ENABLE, |
| intr_mask); |
| mutex_unlock(&sd->lock); |
| |
| return r; |
| } |
| |
| static int syn_test_i2c_wr(struct syn *sd, const u8 value) |
| { |
| int r; |
| |
| r = syn_write_u8(sd, sd->bist->data + BIST_DATA_TEST_NUMBER_CTRL, |
| value); |
| if (r < 0) |
| goto out; |
| |
| r = syn_read_u8(sd, sd->bist->data + BIST_DATA_TEST_NUMBER_CTRL); |
| if (r < 0) |
| goto out; |
| |
| if (r != value) { |
| dev_err(&sd->client->dev, |
| "write verify error: wrote 0x%x got 0x%x\n", |
| value, r); |
| r = -EINVAL; |
| goto out; |
| } |
| |
| r = 0; |
| out: |
| |
| return r; |
| } |
| |
| static int syn_test_i2c(struct syn *sd) |
| { |
| int r; |
| int i; |
| |
| mutex_lock(&sd->lock); |
| |
| for (i = 0; i < 8; i++) { |
| r = syn_test_i2c_wr(sd, 1 << i); |
| if (r < 0) |
| goto out; |
| } |
| |
| for (i = 0; i < 256; i++) { |
| r = syn_test_i2c_wr(sd, i); |
| if (r < 0) |
| goto out; |
| } |
| |
| out: |
| mutex_unlock(&sd->lock); |
| |
| return r; |
| } |
| |
| static int syn_bist_selftest(struct syn *sd, struct bist_test_result *tr) |
| { |
| int r; |
| |
| r = syn_test_i2c(sd); |
| if (r != 0) |
| return r; |
| |
| r = syn_bist_run_test(sd, tr, 0x00); |
| |
| return r; |
| } |
| |
| static ssize_t syn_show_attr_selftest(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct syn *sd = platform_get_drvdata(pdev); |
| const ssize_t size = PAGE_SIZE; |
| ssize_t l = 0; |
| struct bist_test_result tr = { 0, }; |
| int r; |
| |
| if (!sd->bist) { |
| l += snprintf(buf + l, size - l, "Not available\n"); |
| return l; |
| } |
| |
| r = syn_bist_selftest(sd, &tr); |
| if (r != 0) |
| l += snprintf(buf + l, size - l, |
| "FAIL (io error %d)\n", r); |
| else { |
| if (tr.failed == 0) |
| l += snprintf(buf + l, size - l, |
| "PASS\n"); |
| else |
| l += snprintf(buf + l, size - l, |
| "FAIL (test %d, result %d)\n", |
| tr.failed, tr.result); |
| } |
| |
| return l; |
| } |
| |
| static ssize_t syn_store_attr_reset(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct syn *sd = platform_get_drvdata(pdev); |
| |
| mutex_lock(&sd->lock); |
| |
| syn_reset_device(sd); |
| |
| mutex_unlock(&sd->lock); |
| |
| return count; |
| } |
| |
| static ssize_t syn_show_attr_doze(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct syn *sd = platform_get_drvdata(pdev); |
| const ssize_t size = PAGE_SIZE; |
| ssize_t l = 0; |
| int r; |
| |
| mutex_lock(&sd->lock); |
| r = syn_get_nosleep(sd); |
| mutex_unlock(&sd->lock); |
| |
| if (r < 0) |
| goto out; |
| |
| l += snprintf(buf + l, size - l, "%d\n", !r); |
| out: |
| return l; |
| } |
| |
| static ssize_t syn_store_attr_doze(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct syn *sd = platform_get_drvdata(pdev); |
| int val; |
| |
| if (sscanf(buf, "%i", &val) != 1) { |
| dev_info(&sd->client->dev, |
| "error parsing debug\n"); |
| return count; |
| } |
| |
| if (val & (~0x01)) |
| return count; |
| |
| mutex_lock(&sd->lock); |
| syn_set_nosleep(sd, !val); |
| mutex_unlock(&sd->lock); |
| |
| return count; |
| } |
| |
| static ssize_t syn_show_attr_sleepmode(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct syn *sd = platform_get_drvdata(pdev); |
| const ssize_t size = PAGE_SIZE; |
| ssize_t l = 0; |
| int r; |
| |
| mutex_lock(&sd->lock); |
| |
| r = syn_read_u8(sd, sd->control->control + DEVICE_CONTROL_CTRL); |
| if (r < 0) |
| goto out; |
| |
| l += snprintf(buf + l, size - l, "%d\n", (r & 0x03)); |
| out: |
| mutex_unlock(&sd->lock); |
| return l; |
| } |
| |
| static ssize_t syn_store_attr_sleepmode(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct syn *sd = platform_get_drvdata(pdev); |
| int val; |
| int r; |
| |
| if (sscanf(buf, "%i", &val) != 1) { |
| dev_info(&sd->client->dev, |
| "error parsing debug\n"); |
| return count; |
| } |
| |
| if (val & (~0x03)) |
| return count; |
| |
| mutex_lock(&sd->lock); |
| |
| r = syn_read_u8(sd, sd->control->control + DEVICE_CONTROL_CTRL); |
| if (r < 0) |
| goto out; |
| |
| val = (r & (~0x03)) | (val & 0x03); |
| |
| r = syn_write_u8(sd, sd->control->control + DEVICE_CONTROL_CTRL, |
| val); |
| |
| out: |
| mutex_unlock(&sd->lock); |
| |
| return count; |
| } |
| |
| static ssize_t syn_show_attr_proximity(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct syn *sd = platform_get_drvdata(pdev); |
| const ssize_t size = PAGE_SIZE; |
| ssize_t l = 0; |
| int r; |
| |
| mutex_lock(&sd->lock); |
| r = syn_get_proximity_state(sd); |
| mutex_unlock(&sd->lock); |
| |
| if (r < 0) |
| r = 0; |
| |
| l += snprintf(buf + l, size - l, "%d\n", r); |
| |
| return l; |
| } |
| |
| static ssize_t syn_store_attr_proximity(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct syn *sd = platform_get_drvdata(pdev); |
| int val; |
| |
| if (sscanf(buf, "%i", &val) != 1) { |
| dev_info(&sd->client->dev, |
| "error parsing debug\n"); |
| return count; |
| } |
| |
| if (val & (~0x01)) |
| return count; |
| |
| mutex_lock(&sd->lock); |
| syn_set_proximity_state(sd, !!val); |
| mutex_unlock(&sd->lock); |
| |
| return count; |
| } |
| |
| static ssize_t syn_show_attr_sensitivity(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct syn *sd = platform_get_drvdata(pdev); |
| const ssize_t size = PAGE_SIZE; |
| ssize_t l = 0; |
| int i; |
| u8 addr; |
| int r; |
| |
| if (!sd->touch) { |
| l += snprintf(buf + l, size - l, "Not available\n"); |
| return l; |
| } |
| |
| addr = sd->touch->control + TOUCH_CONTROL_SENSOR_MAPPING + |
| sd->touch_caps.max_electrodes; |
| |
| if (sd->touch_caps.has_gestures) |
| addr += 2; |
| |
| mutex_lock(&sd->lock); |
| |
| for (i = 0; i < sd->touch_caps.max_electrodes; i++) { |
| r = syn_read_u8(sd, addr + i); |
| if (r < 0) { |
| l += snprintf(buf + l, size - l, "Read error %d\n", r); |
| goto out; |
| } else |
| l += snprintf(buf + l, size - l, "0x%x ", r); |
| } |
| |
| l += snprintf(buf + l, size - l, "\n"); |
| |
| out: |
| mutex_unlock(&sd->lock); |
| |
| return l; |
| } |
| |
| static ssize_t syn_store_attr_sensitivity(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct syn *sd = platform_get_drvdata(pdev); |
| int r; |
| int addr; |
| int base; |
| int value; |
| |
| if (!sd->touch) |
| return count; |
| |
| if (sscanf(buf, "%d %i", &addr, &value) != 2) { |
| dev_info(&sd->client->dev, |
| "error parsing sensitivity info <addr> <value>\n"); |
| return count; |
| } |
| |
| if (addr >= sd->touch_caps.max_electrodes) { |
| dev_info(&sd->client->dev, |
| "electorode number out of bounds %d > %d\n", addr, |
| sd->touch_caps.max_electrodes); |
| return count; |
| } |
| |
| base = sd->touch->control + TOUCH_CONTROL_SENSOR_MAPPING + |
| sd->touch_caps.max_electrodes; |
| |
| if (sd->touch_caps.has_gestures) |
| base += 2; |
| |
| mutex_lock(&sd->lock); |
| |
| r = syn_write_u8(sd, base + addr, value); |
| if (r < 0) { |
| dev_err(&sd->client->dev, "write failed with %d\n", r); |
| goto out; |
| } |
| |
| r = syn_read_u8(sd, base + addr); |
| if (r < 0) { |
| dev_err(&sd->client->dev, "read failed with %d\n", r); |
| goto out; |
| } |
| |
| if (r != value) { |
| dev_warn(&sd->client->dev, |
| "value verify error 0x%x != 0x%x\n", r, value); |
| } |
| out: |
| mutex_unlock(&sd->lock); |
| |
| return count; |
| } |
| |
| static ssize_t syn_show_attr_sensormap(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct syn *sd = platform_get_drvdata(pdev); |
| const ssize_t size = PAGE_SIZE; |
| ssize_t l = 0; |
| int i; |
| u8 addr; |
| int r; |
| |
| if (!sd->touch) { |
| l += snprintf(buf + l, size - l, "Not available\n"); |
| return l; |
| } |
| |
| addr = sd->touch->control + TOUCH_CONTROL_SENSOR_MAPPING; |
| |
| if (sd->touch_caps.has_gestures) |
| addr += 2; |
| |
| mutex_lock(&sd->lock); |
| |
| for (i = 0; i < sd->touch_caps.max_electrodes; i++) { |
| r = syn_read_u8(sd, addr + i); |
| if (r < 0) { |
| l += snprintf(buf + l, size - l, "Read error %d\n", r); |
| goto out; |
| } else |
| l += snprintf(buf + l, size - l, "%s: %d\n", |
| r & 0x80 ? "Y" : "X", r & 0x1F); |
| } |
| |
| l += snprintf(buf + l, size - l, "\n"); |
| |
| out: |
| mutex_unlock(&sd->lock); |
| |
| return l; |
| } |
| |
| static ssize_t syn_show_attr_reg_dump(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct syn *sd = platform_get_drvdata(pdev); |
| const ssize_t size = PAGE_SIZE; |
| ssize_t l = 0; |
| int i; |
| int r; |
| |
| if (!sd->touch) { |
| l += snprintf(buf + l, size - l, "Not available\n"); |
| return l; |
| } |
| |
| mutex_lock(&sd->lock); |
| |
| for (i = 0; i < 0xff; i++) { |
| r = syn_read_u8(sd, i); |
| if (r < 0) { |
| l += snprintf(buf + l, size - l, |
| "Read error at 0x%x %d\n", i, r); |
| goto out; |
| } else |
| l += snprintf(buf + l, size - l, |
| "0x%02x: 0x%02x (%d)\n", i, r, r); |
| } |
| |
| l += snprintf(buf + l, size - l, "\n"); |
| |
| out: |
| mutex_unlock(&sd->lock); |
| return l; |
| } |
| |
| static ssize_t syn_store_attr_reg_dump(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct syn *sd = platform_get_drvdata(pdev); |
| int addr; |
| int value; |
| |
| if (!sd->touch) |
| return count; |
| |
| if (sscanf(buf, "%i", &addr) != 1) { |
| dev_info(&sd->client->dev, |
| "error parsing addr\n"); |
| return count; |
| } |
| |
| if (addr < 0 || addr > 0xff) { |
| dev_info(&sd->client->dev, |
| "error 0x%x out of bounds\n", addr); |
| return count; |
| } |
| |
| mutex_lock(&sd->lock); |
| |
| value = syn_read_u8(sd, addr); |
| if (value < 0) { |
| dev_info(&sd->client->dev, |
| "error %d reading from 0x%x\n", value, addr); |
| goto out; |
| } |
| |
| dev_info(&sd->client->dev, "0x%x: 0x%x\n", addr, value); |
| |
| out: |
| mutex_unlock(&sd->lock); |
| |
| return count; |
| } |
| |
| static ssize_t syn_show_attr_debug(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct syn *sd = platform_get_drvdata(pdev); |
| const ssize_t size = PAGE_SIZE; |
| ssize_t l = 0; |
| |
| mutex_lock(&sd->lock); |
| |
| l += snprintf(buf + l, size - l, "0x%02x\n", sd->debug_flag); |
| |
| mutex_unlock(&sd->lock); |
| |
| return l; |
| } |
| |
| static ssize_t syn_store_attr_debug(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct syn *sd = platform_get_drvdata(pdev); |
| int flag = 0; |
| |
| if (sscanf(buf, "%i", &flag) != 1) { |
| dev_info(&sd->client->dev, |
| "error parsing debug\n"); |
| return count; |
| } |
| |
| mutex_lock(&sd->lock); |
| |
| sd->debug_flag = flag; |
| |
| mutex_unlock(&sd->lock); |
| |
| return count; |
| } |
| |
| static ssize_t syn_store_attr_latency(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct syn *sd = platform_get_drvdata(pdev); |
| |
| mutex_lock(&sd->lock); |
| |
| sd->t_work_min = ULONG_MAX; |
| sd->t_work_max = 0; |
| sd->t_work = 0; |
| sd->t_work_c = 0; |
| |
| sd->t_wakeup_min = ULONG_MAX; |
| sd->t_wakeup_max = 0; |
| sd->t_wakeup = 0; |
| sd->t_wakeup_c = 0; |
| |
| sd->t_count = 0; |
| |
| mutex_unlock(&sd->lock); |
| |
| return count; |
| } |
| |
| static ssize_t syn_show_attr_latency(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct syn *sd = platform_get_drvdata(pdev); |
| const ssize_t size = PAGE_SIZE; |
| ssize_t l = 0; |
| |
| mutex_lock(&sd->lock); |
| |
| l += snprintf(buf + l, size - l, |
| "count %lu: wakeup %lu (%lu, %lu, %lu), " |
| "work %lu (%lu, %lu, %lu)\n", |
| sd->t_count, |
| sd->t_wakeup, sd->t_wakeup_min, sd->t_wakeup_max, |
| sd->t_count ? (sd->t_wakeup_c / sd->t_count) : 0, |
| sd->t_work, sd->t_work_min, sd->t_work_max, |
| sd->t_count ? (sd->t_work_c / sd->t_count) : 0); |
| |
| mutex_unlock(&sd->lock); |
| |
| return l; |
| } |
| |
| static ssize_t syn_show_attr_virtualized(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct syn *sd = platform_get_drvdata(pdev); |
| const ssize_t size = PAGE_SIZE; |
| ssize_t l = 0; |
| |
| mutex_lock(&sd->lock); |
| |
| l += snprintf(buf + l, size - l, "%d\n", sd->virtualized); |
| |
| mutex_unlock(&sd->lock); |
| |
| return l; |
| } |
| |
| static ssize_t syn_store_attr_virtualized(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct syn *sd = platform_get_drvdata(pdev); |
| int val; |
| |
| if (sscanf(buf, "%i", &val) != 1) { |
| dev_info(&sd->client->dev, |
| "error parsing virtualized\n"); |
| return count; |
| } |
| |
| if (val & (~0x01)) |
| return count; |
| |
| mutex_lock(&sd->lock); |
| sd->virtualized = val; |
| mutex_unlock(&sd->lock); |
| |
| return count; |
| } |
| |
| |
| |
| static DEVICE_ATTR(flash, S_IWUSR | S_IRUGO, |
| syn_show_attr_flash, syn_store_attr_flash); |
| |
| static DEVICE_ATTR(product_id, S_IRUGO, |
| syn_show_attr_product_id, NULL); |
| |
| static DEVICE_ATTR(product_family, S_IRUGO, |
| syn_show_attr_product_family, NULL); |
| |
| static DEVICE_ATTR(firmware_version, S_IRUGO, |
| syn_show_attr_firmware_version, NULL); |
| |
| static DEVICE_ATTR(selftest, S_IRUGO, |
| syn_show_attr_selftest, NULL); |
| |
| static DEVICE_ATTR(reset, S_IWUSR, NULL, syn_store_attr_reset); |
| |
| static DEVICE_ATTR(doze, S_IRUGO | S_IWUSR, |
| syn_show_attr_doze, syn_store_attr_doze); |
| |
| static DEVICE_ATTR(sleepmode, S_IRUGO | S_IWUSR, |
| syn_show_attr_sleepmode, syn_store_attr_sleepmode); |
| |
| static DEVICE_ATTR(proximity, S_IRUGO | S_IWUSR, |
| syn_show_attr_proximity, syn_store_attr_proximity); |
| |
| static DEVICE_ATTR(sensitivity, S_IRUGO | S_IWUSR, |
| syn_show_attr_sensitivity, syn_store_attr_sensitivity); |
| |
| static DEVICE_ATTR(sensormap, S_IRUGO, |
| syn_show_attr_sensormap, NULL); |
| |
| static DEVICE_ATTR(dump, S_IRUGO | S_IWUSR, |
| syn_show_attr_reg_dump, syn_store_attr_reg_dump); |
| |
| static DEVICE_ATTR(debug, S_IRUGO | S_IWUSR, |
| syn_show_attr_debug, syn_store_attr_debug); |
| |
| static DEVICE_ATTR(latency, S_IRUGO | S_IWUSR, |
| syn_show_attr_latency, syn_store_attr_latency); |
| |
| static DEVICE_ATTR(virtualized, S_IRUGO | S_IWUSR, |
| syn_show_attr_virtualized, syn_store_attr_virtualized); |
| |
| static struct attribute *syn_attrs[] = { |
| &dev_attr_flash.attr, |
| &dev_attr_product_id.attr, |
| &dev_attr_product_family.attr, |
| &dev_attr_firmware_version.attr, |
| &dev_attr_selftest.attr, |
| &dev_attr_reset.attr, |
| &dev_attr_doze.attr, |
| &dev_attr_sleepmode.attr, |
| &dev_attr_proximity.attr, |
| &dev_attr_sensitivity.attr, |
| &dev_attr_sensormap.attr, |
| &dev_attr_dump.attr, |
| &dev_attr_debug.attr, |
| &dev_attr_latency.attr, |
| &dev_attr_virtualized.attr, |
| NULL, |
| }; |
| |
| static struct attribute_group syn_attr_group = { |
| .attrs = syn_attrs, |
| }; |
| |
| static void syn_create_sysfs(struct syn *sd) |
| { |
| int r; |
| |
| r = sysfs_create_group(&sd->client->dev.kobj, &syn_attr_group); |
| if (r) { |
| dev_err(&sd->client->dev, |
| "failed to create flash sysfs files\n"); |
| } |
| } |
| |
| static void syn_remove_sysfs(struct syn *sd) |
| { |
| sysfs_remove_group(&sd->client->dev.kobj, &syn_attr_group); |
| } |
| |
| static int syn_read_func_descs(struct syn *sd) |
| { |
| int properties = 0; |
| int fun = 0; |
| int r = 0; |
| int addr = REG_PDT_PROPERTIES; |
| |
| sd->func_descs_valid = 0; |
| sd->interrupt_sources = 0; |
| |
| properties = syn_read_u8(sd, addr--); |
| if (properties < 0) |
| return properties; |
| |
| /* We don't support non standard page select register addr */ |
| if (properties != 0x00) { |
| dev_err(&sd->client->dev, "unsupported pdt\n"); |
| return -ENODEV; |
| } |
| |
| sd->func_desc_num = 0; |
| |
| while (sd->func_desc_num < MAX_FUNC_DESCS) { |
| struct func_desc * const f = &sd->func_desc[sd->func_desc_num]; |
| u8 regs[5]; |
| |
| fun = syn_read_u8(sd, addr); |
| if (fun < 0) |
| return fun; |
| |
| if (fun == 0x00) |
| goto out; |
| |
| if (sd->debug_flag & DFLAG_VERBOSE) |
| dev_info(&sd->client->dev, |
| "found function 0x%x\n", fun); |
| |
| f->num = fun; |
| addr -= 5; |
| r = syn_read_block(sd, addr, regs, 5); |
| if (r != 5) { |
| dev_err(&sd->client->dev, "error reading pdt\n"); |
| return -ENODEV; |
| } |
| |
| f->query = regs[0]; |
| f->command = regs[1]; |
| f->control = regs[2]; |
| f->data = regs[3]; |
| f->intr_sources = regs[4] & 0x07; |
| f->version = (regs[4] >> 5) & 0x3; |
| |
| if (f->intr_sources) { |
| f->intr_start_bit = sd->interrupt_sources; |
| sd->interrupt_sources += f->intr_sources; |
| } |
| |
| sd->func_desc_num++; |
| addr--; |
| } |
| |
| out: |
| /* Put shortcuts in place */ |
| sd->control = syn_get_func_desc(sd, FUNC_DEVICE_CONTROL); |
| sd->flash = syn_get_func_desc(sd, FUNC_FLASH); |
| sd->touch = syn_get_func_desc(sd, FUNC_2D); |
| sd->buttons = syn_get_func_desc(sd, FUNC_BUTTONS); |
| sd->bist = syn_get_func_desc(sd, FUNC_BIST); |
| sd->prox = syn_get_func_desc(sd, FUNC_PROXIMITY); |
| |
| if (sd->touch == NULL) |
| dev_warn(&sd->client->dev, |
| "no 2d functionality found! (in flash mode ?)\n"); |
| |
| if (sd->flash) { |
| r = syn_flash_query_caps(sd); |
| if (r) |
| return r; |
| } else |
| dev_warn(&sd->client->dev, "no flash functionality found!\n"); |
| |
| if (sd->control) { |
| sd->func_descs_valid = 1; |
| return 0; |
| } |
| |
| return -1; |
| } |
| |
| static int syn_register_handlers(struct syn *sd) |
| { |
| int r; |
| |
| r = syn_register_intr_handler(sd, FUNC_DEVICE_CONTROL, |
| syn_isr_device_control); |
| if (r < 0) { |
| dev_err(&sd->client->dev, |
| "no device control, cant continue\n"); |
| return r; |
| } |
| |
| /* |
| * Don't care about return values here. |
| * Install interrupt handlers for every function |
| * (even if they dont exist) |
| */ |
| |
| syn_register_intr_handler(sd, FUNC_BIST, syn_isr_bist); |
| syn_register_intr_handler(sd, FUNC_BUTTONS, syn_isr_buttons); |
| syn_register_intr_handler(sd, FUNC_TIMER, syn_isr_timer); |
| syn_register_intr_handler(sd, FUNC_2D, syn_isr_2d); |
| syn_register_intr_handler(sd, FUNC_FLASH, syn_isr_flash); |
| syn_register_intr_handler(sd, FUNC_PROXIMITY, syn_isr_proximity); |
| |
| return 0; |
| } |
| |
| static int syn_touch_get_max_pos(struct syn *sd, u8 reg) |
| { |
| int data; |
| |
| data = syn_read_u16(sd, sd->touch->control + reg); |
| if (data < 0) |
| return data; |
| |
| return data & 0x00000fff; |
| } |
| |
| static int syn_touch_get_max_x_pos(struct syn *sd) |
| { |
| return syn_touch_get_max_pos(sd, TOUCH_CONTROL_SENSOR_MAX_X); |
| } |
| |
| static int syn_touch_get_max_y_pos(struct syn *sd) |
| { |
| return syn_touch_get_max_pos(sd, TOUCH_CONTROL_SENSOR_MAX_Y); |
| } |
| |
| static int syn_button_query_caps(struct syn *sd) |
| { |
| int r; |
| |
| r = syn_read_u8(sd, sd->buttons->query + BUTTON_QUERY_BUTTON_COUNT); |
| if (r < 0) |
| return r; |
| |
| if (r > MAX_BUTTONS) |
| return -EINVAL; |
| |
| sd->button_caps.button_count = r & BUTTON_QUERY_BUTTON_MASK; |
| |
| return 0; |
| } |
| |
| static int syn_touch_query_caps(struct syn *sd) |
| { |
| int r; |
| u8 data[TOUCH_QUERY_LEN] = {0}; |
| |
| if (sd->touch == NULL) |
| return -ENODEV; |
| |
| r = syn_read_block(sd, sd->touch->query + TOUCH_QUERY_NUM_SENSORS, |
| data, TOUCH_QUERY_LEN); |
| if (r < 0) |
| return r; |
| |
| if (data[0] != 0) { |
| dev_err(&sd->client->dev, "no support for multiple sensors\n"); |
| return -ENODEV; |
| } |
| |
| sd->touch_caps.is_configurable = (data[1] & (1 << 7)) ? 1 : 0; |
| sd->touch_caps.has_gestures = (data[1] & (1 << 5)) ? 1 : 0; |
| sd->touch_caps.has_abs_mode = (data[1] & (1 << 4)) ? 1 : 0; |
| sd->touch_caps.has_rel_mode = (data[1] & (1 << 3)) ? 1 : 0; |
| sd->touch_caps.finger_count = (data[1] & 0x07) + 1; |
| sd->touch_caps.x_electrodes = (data[2] & 0x1f); |
| sd->touch_caps.y_electrodes = (data[3] & 0x1f); |
| sd->touch_caps.max_electrodes = (data[4] & 0x1f); |
| sd->touch_caps.abs_data_size = (data[5] & 0x03); |
| |
| if (sd->touch_caps.finger_count > MAX_TOUCH_POINTS || |
| sd->touch_caps.has_rel_mode || |
| /* sd->touch_caps.has_gestures || */ |
| sd->touch_caps.abs_data_size) { |
| dev_err(&sd->client->dev, "no support for this sensor\n"); |
| return -ENODEV; |
| } |
| |
| r = syn_touch_get_max_x_pos(sd); |
| if (r < 0) |
| return r; |
| |
| sd->touch_caps.max_x = r; |
| |
| r = syn_touch_get_max_y_pos(sd); |
| if (r < 0) |
| return r; |
| |
| sd->touch_caps.max_y = r; |
| |
| return 0; |
| } |
| |
| static int syn_reset_device(struct syn *sd) |
| { |
| int r; |
| |
| /* |
| * We get called from various contexts, for example after firmware |
| * change. Resetting with state registermap would be disastrous |
| * so we reread everything in order to be sure to write |
| * to correct register. |
| */ |
| r = syn_read_func_descs(sd); |
| |
| if (!sd->control) { |
| dev_err(&sd->client->dev, "no control. can't reset!\n"); |
| return -1; |
| } |
| |
| dev_info(&sd->client->dev, "resetting device (reg 0x%x)\n", |
| sd->control->command + DEVICE_CONTROL_COMMAND); |
| |
| r = syn_write_u8(sd, sd->control->control + DEVICE_CONTROL_INTR_ENABLE, |
| 0); |
| |
| r = syn_control_data_read(sd, DEVICE_CONTROL_DATA_INTR_STATUS); |
| |
| sd->func_descs_valid = 0; |
| |
| r = syn_write_u8(sd, sd->control->command + |
| DEVICE_CONTROL_COMMAND, DEVICE_COMMAND_RESET); |
| |
| r = syn_wait_for_attn(sd, 100 * 1000, 0); |
| r = syn_wait_for_attn(sd, 100 * 1000, 1); |
| r = syn_wait_for_attn(sd, 500 * 1000, 0); |
| |
| return 0; |
| } |
| |
| static int syn_initialize(struct syn *sd) |
| { |
| int r; |
| char prod_id[PRODUCT_ID_LEN + 1]; |
| int prod_family; |
| int prod_fw_version; |
| |
| syn_clear_device_state(sd); |
| |
| r = syn_read_u8(sd, REG_PAGE_SELECT); |
| if (r < 0) { |
| dev_err(&sd->client->dev, "error reading page select\n"); |
| goto err_out; |
| } |
| |
| if (r != 0x00) { |
| dev_err(&sd->client->dev, "page select non zero\n"); |
| r = -ENODEV; |
| goto err_out; |
| } |
| |
| r = syn_read_func_descs(sd); |
| if (r < 0) { |
| dev_err(&sd->client->dev, "error reading func descs\n"); |
| goto err_out; |
| } |
| |
| if (!sd->control) { |
| dev_err(&sd->client->dev, "no control functions found\n"); |
| r = -ENODEV; |
| goto err_out; |
| } |
| |
| /* Clear the Unconfigured bit */ |
| r = syn_write_u8(sd, sd->control->control + DEVICE_CONTROL_CTRL, |
| DEVICE_CONTROL_CONFIGURED); |
| |
| if (!sd->bist) |
| dev_warn(&sd->client->dev, "no bist capabilities found\n"); |
| |
| if (sd->touch) { |
| r = syn_touch_query_caps(sd); |
| if (r < 0) { |
| dev_err(&sd->client->dev, |
| "error reading touch capabilities\n"); |
| sd->touch = NULL; |
| } |
| } else { |
| dev_err(&sd->client->dev, "no touch capabilities found\n"); |
| } |
| |
| if (sd->buttons) { |
| r = syn_button_query_caps(sd); |
| if (r < 0) { |
| dev_err(&sd->client->dev, |
| "error reading button capabilities\n"); |
| sd->buttons = NULL; |
| } |
| } else { |
| dev_err(&sd->client->dev, "no button capabilities found\n"); |
| } |
| |
| r = syn_read_block(sd, sd->control->query + |
| DEVICE_CONTROL_QUERY_PROD_ID, |
| prod_id, PRODUCT_ID_LEN); |
| |
| if (r != PRODUCT_ID_LEN) { |
| dev_err(&sd->client->dev, "unable to read product id\n"); |
| r = -ENODEV; |
| goto err_out; |
| } |
| prod_id[r] = 0; |
| |
| prod_family = syn_control_query_read(sd, |
| DEVICE_CONTROL_QUERY_PROD_FAMILY); |
| if (prod_family < 0) { |
| dev_err(&sd->client->dev, "unable to read product family\n"); |
| goto err_out; |
| } |
| |
| prod_fw_version = syn_control_query_read(sd, |
| DEVICE_CONTROL_QUERY_FW_VER); |
| if (prod_fw_version < 0) { |
| dev_err(&sd->client->dev, "unable to read product family\n"); |
| goto err_out; |
| } |
| |
| printk(KERN_INFO DRIVER_NAME ": product ID: %s family:%d fw:%d\n", |
| prod_id, prod_family, prod_fw_version); |
| |
| r = syn_register_handlers(sd); |
| if (r) { |
| dev_err(&sd->client->dev, "failed to register_handlers\n"); |
| r = -ENODEV; |
| goto err_out; |
| } |
| |
| if (sd->touch) { |
| r = syn_register_input_devices(sd, sd->touch_caps.finger_count); |
| if (r) { |
| dev_err(&sd->client->dev, |
| "failed to register input devices\n"); |
| r = -ENODEV; |
| goto err_out; |
| } |
| } |
| |
| if (sd->prox) { |
| /* By default disable it */ |
| syn_set_proximity_state(sd, 0); |
| } |
| |
| r = syn_control_data_read(sd, DEVICE_CONTROL_DATA_STATUS); |
| if (r < 0) { |
| dev_err(&sd->client->dev, "error %d reading device status\n", |
| r); |
| } |
| |
| /* |
| * If we get reset during initialization, unconfigured should be on |
| */ |
| if (r & (1 << 7)) { |
| dev_warn(&sd->client->dev, "lost config during initialize\n"); |
| sd->failed_inits++; |
| syn_reset_device(sd); |
| goto err_out; |
| } |
| |
| sd->failed_inits = 0; |
| return 0; |
| |
| err_out: |
| return r; |
| } |
| |
| static int syn_probe(struct i2c_client *client, const struct i2c_device_id *id) |
| { |
| struct syn *sd; |
| struct tm12xx_ts_platform_data *pdata; |
| int r; |
| int man_id; |
| |
| sd = kzalloc(sizeof(struct syn), GFP_KERNEL); |
| if (sd == NULL) |
| return -ENOMEM; |
| |
| INIT_WORK(&sd->isr_work, syn_isr_work); |
| i2c_set_clientdata(client, sd); |
| sd->client = client; |
| |
| mutex_init(&sd->lock); |
| |
| sd->t_work_min = ULONG_MAX; |
| sd->t_wakeup_min = ULONG_MAX; |
| |
| sd->wq = create_singlethread_workqueue("tm12xx_wq"); |
| if (!sd->wq) { |
| r = -ENOMEM; |
| goto err_free_dev; |
| } |
| |
| pdata = sd->client->dev.platform_data; |
| if (!pdata) { |
| dev_err(&client->dev, "no platform data found\n"); |
| r = -ENODEV; |
| goto err_free_dev; |
| } |
| |
| sd->gpio_intr = pdata->gpio_intr; |
| |
| if (pdata->suspend_state == SYNTM12XX_SLEEP_ON_SUSPEND) |
| sd->suspend_mode = DEVICE_CONTROL_SLEEP_SENSOR; |
| else |
| sd->suspend_mode = DEVICE_CONTROL_SLEEP_NORMAL; |
| |
| r = syn_read_func_descs(sd); |
| if (r < 0) { |
| dev_err(&client->dev, "error reading func descs\n"); |
| goto err_free_dev; |
| } |
| |
| r = syn_write_u8(sd, sd->control->control + DEVICE_CONTROL_INTR_ENABLE, |
| 0); |
| if (r < 0) |
| goto err_free_dev; |
| |
| r = syn_control_data_read(sd, DEVICE_CONTROL_DATA_INTR_STATUS); |
| if (r < 0) |
| goto err_free_dev; |
| |
| man_id = syn_control_query_read(sd, DEVICE_CONTROL_QUERY_MANID); |
| if (man_id < 0) { |
| dev_dbg(&client->dev, "unable to get manufacturer id\n"); |
| r = -ENODEV; |
| goto err_free_dev; |
| } |
| |
| printk(KERN_INFO DRIVER_NAME ": " DRIVER_DESC |
| " found man id %x (%d)\n", man_id, man_id); |
| |
| r = gpio_request(sd->gpio_intr, "Synaptic TM12XX Interrupt"); |
| if (r < 0) { |
| dev_dbg(&client->dev, "unable to get INT GPIO\n"); |
| r = -ENODEV; |
| goto err_free_dev; |
| } |
| |
| gpio_direction_input(sd->gpio_intr); |
| |
| r = syn_register_intr_handler(sd, FUNC_DEVICE_CONTROL, |
| syn_isr_device_control); |
| if (r < 0) { |
| dev_err(&sd->client->dev, |
| "no device control, can't continue\n"); |
| r = -ENODEV; |
| goto err_free_int_gpio; |
| } |
| |
| mutex_lock(&sd->lock); |
| |
| r = request_irq(gpio_to_irq(sd->gpio_intr), syn_isr, |
| IRQF_DISABLED | IRQF_TRIGGER_LOW | IRQF_TRIGGER_FALLING, |
| DRIVER_NAME, sd); |
| if (r) { |
| dev_dbg(&client->dev, "can't get IRQ %d (%d), err %d\n", |
| gpio_to_irq(sd->gpio_intr), sd->gpio_intr, r); |
| goto err_release_mutex; |
| } |
| |
| syn_create_sysfs(sd); |
| |
| /* Initialize thru reset */ |
| r = syn_reset_device(sd); |
| if (r < 0) { |
| dev_err(&client->dev, "error in reset device\n"); |
| goto err_free_irq; |
| } |
| |
| |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| sd->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; |
| sd->early_suspend.suspend = syn_ts_early_suspend; |
| sd->early_suspend.resume = syn_ts_late_resume; |
| register_early_suspend(&sd->early_suspend); |
| #endif |
| mutex_unlock(&sd->lock); |
| |
| return 0; |
| |
| err_free_irq: |
| free_irq(gpio_to_irq(sd->gpio_intr), sd); |
| |
| err_release_mutex: |
| mutex_unlock(&sd->lock); |
| |
| err_free_int_gpio: |
| gpio_free(sd->gpio_intr); |
| |
| err_free_dev: |
| kfree(sd); |
| sd = NULL; |
| return r; |
| } |
| |
| static int __exit syn_remove(struct i2c_client *client) |
| { |
| struct syn *sd = i2c_get_clientdata(client); |
| int i; |
| |
| mutex_lock(&sd->lock); |
| |
| if (sd->control) |
| syn_write_u8(sd, |
| sd->control->control + DEVICE_CONTROL_INTR_ENABLE, |
| 0); |
| |
| free_irq(gpio_to_irq(sd->gpio_intr), sd); |
| |
| mutex_unlock(&sd->lock); |
| |
| destroy_workqueue(sd->wq); |
| sd->wq = NULL; |
| |
| syn_remove_sysfs(sd); |
| |
| for (i = 0; i < MAX_TOUCH_POINTS; i++) { |
| if (sd->tp[i].idev) { |
| input_unregister_device(sd->tp[i].idev); |
| input_free_device(sd->tp[i].idev); |
| sd->tp[i].idev = NULL; |
| } |
| } |
| |
| gpio_free(sd->gpio_intr); |
| |
| kfree(sd); |
| i2c_set_clientdata(client, NULL); |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_PM |
| static int syn_suspend(struct i2c_client *client, pm_message_t msg) |
| { |
| struct syn *sd = i2c_get_clientdata(client); |
| int r; |
| |
| mutex_lock(&sd->lock); |
| |
| r = syn_read_u8(sd, sd->control->control + DEVICE_CONTROL_CTRL); |
| /* If we fail to get previous finetuned power mode, we don't care */ |
| if (r < 0) |
| goto out; |
| |
| sd->device_control_ctrl = r & 0xff; |
| |
| r = syn_write_u8(sd, sd->control->control + DEVICE_CONTROL_CTRL, |
| sd->suspend_mode); |
| out: |
| mutex_unlock(&sd->lock); |
| |
| return 0; |
| } |
| |
| static int syn_resume(struct i2c_client *client) |
| { |
| struct syn *sd = i2c_get_clientdata(client); |
| int r; |
| |
| mutex_lock(&sd->lock); |
| r = syn_write_u8(sd, sd->control->control + DEVICE_CONTROL_CTRL, |
| sd->device_control_ctrl); |
| if (r < 0) |
| dev_err(&sd->client->dev, "error %d restoring device state\n", |
| r); |
| mutex_unlock(&sd->lock); |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| static void syn_ts_early_suspend(struct early_suspend *handler) |
| { |
| struct syn *sd; |
| |
| sd = container_of(handler, struct syn, early_suspend); |
| syn_suspend(sd->client, PMSG_SUSPEND); |
| } |
| |
| static void syn_ts_late_resume(struct early_suspend *handler) |
| { |
| struct syn *sd; |
| |
| sd = container_of(handler, struct syn, early_suspend); |
| syn_resume(sd->client); |
| } |
| #endif |
| #endif |
| |
| static const struct i2c_device_id syn_id[] = { |
| { "tm12xx_ts_primary", 0 }, |
| { "tm12xx_ts_secondary", 1}, |
| { } |
| }; |
| |
| static struct i2c_driver syn_i2c_driver = { |
| .driver = { |
| .name = DRIVER_NAME, |
| }, |
| .probe = syn_probe, |
| .remove = __exit_p(syn_remove), |
| .id_table = syn_id, |
| |
| #ifdef CONFIG_PM |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| .suspend = NULL, |
| .resume = NULL, |
| #else |
| .suspend = syn_suspend, |
| .resume = syn_resume, |
| #endif |
| #endif |
| }; |
| |
| static int __init syn_init(void) |
| { |
| int r; |
| |
| r = i2c_add_driver(&syn_i2c_driver); |
| if (r < 0) { |
| printk(KERN_WARNING DRIVER_NAME |
| " driver registration failed\n"); |
| return r; |
| } |
| |
| return 0; |
| } |
| |
| static void __exit syn_exit(void) |
| { |
| i2c_del_driver(&syn_i2c_driver); |
| } |
| |
| module_init(syn_init); |
| module_exit(syn_exit); |
| |
| MODULE_AUTHOR("Mika Kuoppala <mika.kuoppala@nokia.com>"); |
| MODULE_DESCRIPTION("Synaptic TM12xx Touch controller driver"); |
| MODULE_LICENSE("GPL"); |