diff options
author | David S. Miller <davem@davemloft.net> | 2013-03-01 00:09:49 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2013-03-01 00:09:49 -0500 |
commit | f757c523d3f87725afc7cadbad48cb3f5311c11e (patch) | |
tree | 67622a8494ce258d526e73a52bc011049bb95eb1 | |
download | chromebook_pixel-f757c523d3f87725afc7cadbad48cb3f5311c11e.tar.gz |
Initial import.
-rw-r--r-- | Makefile | 16 | ||||
-rw-r--r-- | atmel_mxt_ts.c | 2914 | ||||
-rw-r--r-- | chromeos_laptop.c | 478 |
3 files changed, 3408 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a5aea83 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +ifneq ($(KERNELRELEASE),) +obj-m := chromeos_laptop.o atmel_mxt_ts.o +else +KDIR ?= /lib/modules/`uname -r`/build + +all: chromeos_laptop atmel_mxt_ts + +chromeos_laptop: chromeos_laptop.c + $(MAKE) -C $(KDIR) M=$$PWD + +atmel_mxt_ts: atmel_mxt_ts.c + $(MAKE) -C $(KDIR) M=$$PWD + +clean: + rm -f chromeos_laptop *.o *.ko modules.order Module.symvers *.mod.c +endif diff --git a/atmel_mxt_ts.c b/atmel_mxt_ts.c new file mode 100644 index 0000000..f5d841c --- /dev/null +++ b/atmel_mxt_ts.c @@ -0,0 +1,2914 @@ +/* + * Atmel maXTouch Touchscreen driver + * + * Copyright (C) 2010 Samsung Electronics Co.Ltd + * Author: Joonyoung Shim <jy0922.shim@samsung.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/async.h> +#include <linux/debugfs.h> +#include <linux/completion.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/i2c.h> +#include <linux/i2c/atmel_mxt_ts.h> +#include <linux/input/mt.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +/* Version */ +#define MXT_VER_20 20 +#define MXT_VER_21 21 +#define MXT_VER_22 22 + +/* Slave addresses */ +#define MXT_APP_LOW 0x4a +#define MXT_APP_HIGH 0x4b +/* + * MXT_BOOT_LOW disagrees with Atmel documentation, but has been + * updated to support new touch hardware that pairs 0x26 boot with 0x4a app. + */ +#define MXT_BOOT_LOW 0x26 +#define MXT_BOOT_HIGH 0x25 + +/* Firmware */ +#define MXT_FW_NAME "maxtouch.fw" + +/* Config file */ +#define MXT_CONFIG_NAME "maxtouch.cfg" + +/* Configuration Data */ +#define MXT_CONFIG_VERSION "OBP_RAW V1" + +/* Registers */ +#define MXT_INFO 0x00 +#define MXT_FAMILY_ID 0x00 +#define MXT_VARIANT_ID 0x01 +#define MXT_VERSION 0x02 +#define MXT_BUILD 0x03 +#define MXT_MATRIX_X_SIZE 0x04 +#define MXT_MATRIX_Y_SIZE 0x05 +#define MXT_OBJECT_NUM 0x06 +#define MXT_OBJECT_START 0x07 + +#define MXT_OBJECT_SIZE 6 + +/* Object types */ +#define MXT_DEBUG_DIAGNOSTIC_T37 37 +#define MXT_GEN_MESSAGE_T5 5 +#define MXT_GEN_COMMAND_T6 6 +#define MXT_GEN_POWER_T7 7 +#define MXT_GEN_ACQUIRE_T8 8 +#define MXT_GEN_DATASOURCE_T53 53 +#define MXT_TOUCH_MULTI_T9 9 +#define MXT_TOUCH_KEYARRAY_T15 15 +#define MXT_TOUCH_PROXIMITY_T23 23 +#define MXT_TOUCH_PROXKEY_T52 52 +#define MXT_PROCI_GRIPFACE_T20 20 +#define MXT_PROCG_NOISE_T22 22 +#define MXT_PROCI_ONETOUCH_T24 24 +#define MXT_PROCI_TWOTOUCH_T27 27 +#define MXT_PROCI_GRIP_T40 40 +#define MXT_PROCI_PALM_T41 41 +#define MXT_PROCI_TOUCHSUPPRESSION_T42 42 +#define MXT_PROCI_STYLUS_T47 47 +#define MXT_PROCG_NOISESUPPRESSION_T48 48 +#define MXT_PROCI_ADAPTIVETHRESHOLD_T55 55 +#define MXT_PROCI_SHIELDLESS_T56 56 +#define MXT_PROCI_EXTRATOUCHSCREENDATA_T57 57 +#define MXT_PROCG_NOISESUPPRESSION_T62 62 +#define MXT_SPT_COMMSCONFIG_T18 18 +#define MXT_SPT_GPIOPWM_T19 19 +#define MXT_SPT_SELFTEST_T25 25 +#define MXT_SPT_CTECONFIG_T28 28 +#define MXT_SPT_USERDATA_T38 38 +#define MXT_SPT_DIGITIZER_T43 43 +#define MXT_SPT_MESSAGECOUNT_T44 44 +#define MXT_SPT_CTECONFIG_T46 46 +#define MXT_SPT_TIMER_T61 61 + +/* MXT_GEN_COMMAND_T6 field */ +#define MXT_COMMAND_RESET 0 +#define MXT_COMMAND_BACKUPNV 1 +#define MXT_COMMAND_CALIBRATE 2 +#define MXT_COMMAND_REPORTALL 3 +#define MXT_COMMAND_DIAGNOSTIC 5 + +#define MXT_T6_CMD_PAGE_UP 0x01 +#define MXT_T6_CMD_PAGE_DOWN 0x02 +#define MXT_T6_CMD_DELTAS 0x10 +#define MXT_T6_CMD_REFS 0x11 +#define MXT_T6_CMD_DEVICE_ID 0x80 +#define MXT_T6_CMD_TOUCH_THRESH 0xF4 + +/* MXT_GEN_POWER_T7 field */ +#define MXT_POWER_IDLEACQINT 0 +#define MXT_POWER_ACTVACQINT 1 +#define MXT_POWER_ACTV2IDLETO 2 + +/* MXT_GEN_ACQUIRE_T8 field */ +#define MXT_ACQUIRE_CHRGTIME 0 +#define MXT_ACQUIRE_TCHDRIFT 2 +#define MXT_ACQUIRE_DRIFTST 3 +#define MXT_ACQUIRE_TCHAUTOCAL 4 +#define MXT_ACQUIRE_SYNC 5 +#define MXT_ACQUIRE_ATCHCALST 6 +#define MXT_ACQUIRE_ATCHCALSTHR 7 + +/* MXT_TOUCH_MULTI_T9 field */ +#define MXT_TOUCH_CTRL 0 +#define MXT_TOUCH_XORIGIN 1 +#define MXT_TOUCH_YORIGIN 2 +#define MXT_TOUCH_XSIZE 3 +#define MXT_TOUCH_YSIZE 4 +#define MXT_TOUCH_BLEN 6 +#define MXT_TOUCH_TCHTHR 7 +#define MXT_TOUCH_TCHDI 8 +#define MXT_TOUCH_ORIENT 9 +#define MXT_TOUCH_MOVHYSTI 11 +#define MXT_TOUCH_MOVHYSTN 12 +#define MXT_TOUCH_NUMTOUCH 14 +#define MXT_TOUCH_MRGHYST 15 +#define MXT_TOUCH_MRGTHR 16 +#define MXT_TOUCH_AMPHYST 17 +#define MXT_TOUCH_XRANGE_LSB 18 +#define MXT_TOUCH_XRANGE_MSB 19 +#define MXT_TOUCH_YRANGE_LSB 20 +#define MXT_TOUCH_YRANGE_MSB 21 +#define MXT_TOUCH_XLOCLIP 22 +#define MXT_TOUCH_XHICLIP 23 +#define MXT_TOUCH_YLOCLIP 24 +#define MXT_TOUCH_YHICLIP 25 +#define MXT_TOUCH_XEDGECTRL 26 +#define MXT_TOUCH_XEDGEDIST 27 +#define MXT_TOUCH_YEDGECTRL 28 +#define MXT_TOUCH_YEDGEDIST 29 +#define MXT_TOUCH_JUMPLIMIT 30 + +/* MXT_TOUCH_CTRL bits */ +#define MXT_TOUCH_CTRL_ENABLE (1 << 0) +#define MXT_TOUCH_CTRL_RPTEN (1 << 1) +#define MXT_TOUCH_CTRL_DISAMP (1 << 2) +#define MXT_TOUCH_CTRL_DISVECT (1 << 3) +#define MXT_TOUCH_CTRL_DISMOVE (1 << 4) +#define MXT_TOUCH_CTRL_DISREL (1 << 5) +#define MXT_TOUCH_CTRL_DISPRESS (1 << 6) +#define MXT_TOUCH_CTRL_SCANEN (1 << 7) +#define MXT_TOUCH_CTRL_OPERATIONAL (MXT_TOUCH_CTRL_ENABLE | \ + MXT_TOUCH_CTRL_SCANEN | \ + MXT_TOUCH_CTRL_RPTEN) +#define MXT_TOUCH_CTRL_SCANNING (MXT_TOUCH_CTRL_ENABLE | \ + MXT_TOUCH_CTRL_SCANEN) + +/* MXT_PROCI_GRIPFACE_T20 field */ +#define MXT_GRIPFACE_CTRL 0 +#define MXT_GRIPFACE_XLOGRIP 1 +#define MXT_GRIPFACE_XHIGRIP 2 +#define MXT_GRIPFACE_YLOGRIP 3 +#define MXT_GRIPFACE_YHIGRIP 4 +#define MXT_GRIPFACE_MAXTCHS 5 +#define MXT_GRIPFACE_SZTHR1 7 +#define MXT_GRIPFACE_SZTHR2 8 +#define MXT_GRIPFACE_SHPTHR1 9 +#define MXT_GRIPFACE_SHPTHR2 10 +#define MXT_GRIPFACE_SUPEXTTO 11 + +/* MXT_PROCI_NOISE field */ +#define MXT_NOISE_CTRL 0 +#define MXT_NOISE_OUTFLEN 1 +#define MXT_NOISE_GCAFUL_LSB 3 +#define MXT_NOISE_GCAFUL_MSB 4 +#define MXT_NOISE_GCAFLL_LSB 5 +#define MXT_NOISE_GCAFLL_MSB 6 +#define MXT_NOISE_ACTVGCAFVALID 7 +#define MXT_NOISE_NOISETHR 8 +#define MXT_NOISE_FREQHOPSCALE 10 +#define MXT_NOISE_FREQ0 11 +#define MXT_NOISE_FREQ1 12 +#define MXT_NOISE_FREQ2 13 +#define MXT_NOISE_FREQ3 14 +#define MXT_NOISE_FREQ4 15 +#define MXT_NOISE_IDLEGCAFVALID 16 + +/* MXT_SPT_COMMSCONFIG_T18 */ +#define MXT_COMMS_CTRL 0 +#define MXT_COMMS_CMD 1 + +/* MXT_SPT_CTECONFIG_T28 field */ +#define MXT_CTE_CTRL 0 +#define MXT_CTE_CMD 1 +#define MXT_CTE_MODE 2 +#define MXT_CTE_IDLEGCAFDEPTH 3 +#define MXT_CTE_ACTVGCAFDEPTH 4 +#define MXT_CTE_VOLTAGE 5 + +#define MXT_VOLTAGE_DEFAULT 2700000 +#define MXT_VOLTAGE_STEP 10000 + +/* Define for MXT_GEN_COMMAND_T6 */ +#define MXT_BOOT_VALUE 0xa5 +#define MXT_BACKUP_VALUE 0x55 +#define MXT_BACKUP_TIME 270 /* msec */ +#define MXT_RESET_TIME 350 /* msec */ +#define MXT_CAL_TIME 25 /* msec */ + +#define MXT_FWRESET_TIME 500 /* msec */ + +/* Default value for acquisition interval when in suspend mode*/ +#define MXT_SUSPEND_ACQINT_VALUE 32 /* msec */ + +/* MXT_SPT_GPIOPWM_T19 field */ +#define MXT_GPIO0_MASK 0x04 +#define MXT_GPIO1_MASK 0x08 +#define MXT_GPIO2_MASK 0x10 +#define MXT_GPIO3_MASK 0x20 + +/* Command to unlock bootloader */ +#define MXT_UNLOCK_CMD_MSB 0xaa +#define MXT_UNLOCK_CMD_LSB 0xdc + +/* Bootloader mode status */ +#define MXT_WAITING_BOOTLOAD_CMD 0xc0 /* valid 7 6 bit only */ +#define MXT_WAITING_FRAME_DATA 0x80 /* valid 7 6 bit only */ +#define MXT_FRAME_CRC_CHECK 0x02 +#define MXT_FRAME_CRC_FAIL 0x03 +#define MXT_FRAME_CRC_PASS 0x04 +#define MXT_APP_CRC_FAIL 0x40 /* valid 7 8 bit only */ +#define MXT_BOOT_STATUS_MASK 0x3f + +/* Touch status */ +#define MXT_UNGRIP (1 << 0) +#define MXT_SUPPRESS (1 << 1) +#define MXT_AMP (1 << 2) +#define MXT_VECTOR (1 << 3) +#define MXT_MOVE (1 << 4) +#define MXT_RELEASE (1 << 5) +#define MXT_PRESS (1 << 6) +#define MXT_DETECT (1 << 7) + +/* Touch orient bits */ +#define MXT_XY_SWITCH (1 << 0) +#define MXT_X_INVERT (1 << 1) +#define MXT_Y_INVERT (1 << 2) + +#define MXT_MAX_FINGER 10 + +/* For CMT (must match XRANGE/YRANGE as defined in board config */ +#define MXT_PIXELS_PER_MM 20 + +struct mxt_cfg_file_hdr { + bool valid; + u32 info_crc; + u32 cfg_crc; +}; + +struct mxt_cfg_file_line { + struct list_head list; + u16 addr; + u8 size; + u8 *content; +}; + +struct mxt_info { + u8 family_id; + u8 variant_id; + u8 version; + u8 build; + u8 matrix_xsize; + u8 matrix_ysize; + u8 object_num; +}; + +struct mxt_object { + u8 type; + u16 start_address; + u16 size; + u16 instances; + u8 num_report_ids; +}; + +struct mxt_message { + u8 reportid; + u8 message[7]; +}; + +/* Each client has this additional data */ +struct mxt_data { + struct i2c_client *client; + struct input_dev *input_dev; + const struct mxt_platform_data *pdata; + struct mxt_object *object_table; + struct mxt_info info; + bool is_tp; + + bool irq_wake; /* irq wake is enabled */ + + /* for fw update in bootloader */ + struct completion bl_completion; + + /* for auto-calibration in suspend */ + struct completion auto_cal_completion; + + unsigned int irq; + unsigned int max_x; + unsigned int max_y; + + /* max touchscreen area in terms of pixels and channels */ + unsigned int max_area_pixels; + unsigned int max_area_channels; + + u32 info_csum; + u32 config_csum; + + /* Cached parameters from object table */ + u16 T5_address; + u8 T6_reportid; + u8 T9_reportid_min; + u8 T9_reportid_max; + u8 T19_reportid; + u16 T44_address; + + /* Saved T7 configuration + * [0] = IDLEACQINT + * [1] = ACTVACQINT + * [2] = ACTV2IDLETO + */ + u8 T7_config[3]; + bool T7_config_valid; + + /* T7 IDLEACQINT & ACTVACQINT setting when in suspend mode*/ + u8 suspend_acq_interval; + + /* Saved T9 Ctrl field */ + u8 T9_ctrl; + bool T9_ctrl_valid; + + /* per-instance debugfs root */ + struct dentry *dentry_dev; + struct dentry *dentry_deltas; + struct dentry *dentry_refs; + struct dentry *dentry_object; + + /* Protect access to the T37 object buffer, used by debugfs */ + struct mutex T37_buf_mutex; + u8 *T37_buf; + size_t T37_buf_size; + + /* Protect access to the object register buffer */ + struct mutex object_str_mutex; + char *object_str; + size_t object_str_size; + + /* firmware file name */ + char *fw_file; + + /* config file name */ + char *config_file; + + /* map for the tracking id currently being used */ + bool current_id[MXT_MAX_FINGER]; +}; + +/* global root node of the atmel_mxt_ts debugfs directory. */ +static struct dentry *mxt_debugfs_root; + +static int mxt_initialize(struct mxt_data *data); +static int mxt_input_dev_create(struct mxt_data *data); + +static bool mxt_object_readable(unsigned int type) +{ + switch (type) { + case MXT_GEN_COMMAND_T6: + case MXT_GEN_POWER_T7: + case MXT_GEN_ACQUIRE_T8: + case MXT_GEN_DATASOURCE_T53: + case MXT_TOUCH_MULTI_T9: + case MXT_TOUCH_KEYARRAY_T15: + case MXT_TOUCH_PROXIMITY_T23: + case MXT_TOUCH_PROXKEY_T52: + case MXT_PROCI_GRIPFACE_T20: + case MXT_PROCG_NOISE_T22: + case MXT_PROCI_ONETOUCH_T24: + case MXT_PROCI_TWOTOUCH_T27: + case MXT_PROCI_GRIP_T40: + case MXT_PROCI_PALM_T41: + case MXT_PROCI_TOUCHSUPPRESSION_T42: + case MXT_PROCI_STYLUS_T47: + case MXT_PROCG_NOISESUPPRESSION_T48: + case MXT_PROCI_ADAPTIVETHRESHOLD_T55: + case MXT_PROCI_SHIELDLESS_T56: + case MXT_PROCI_EXTRATOUCHSCREENDATA_T57: + case MXT_PROCG_NOISESUPPRESSION_T62: + case MXT_SPT_COMMSCONFIG_T18: + case MXT_SPT_GPIOPWM_T19: + case MXT_SPT_SELFTEST_T25: + case MXT_SPT_CTECONFIG_T28: + case MXT_DEBUG_DIAGNOSTIC_T37: + case MXT_SPT_DIGITIZER_T43: + case MXT_SPT_CTECONFIG_T46: + case MXT_SPT_TIMER_T61: + case MXT_SPT_USERDATA_T38: + return true; + default: + return false; + } +} + +static bool mxt_object_writable(unsigned int type) +{ + switch (type) { + case MXT_GEN_COMMAND_T6: + case MXT_GEN_POWER_T7: + case MXT_GEN_ACQUIRE_T8: + case MXT_TOUCH_MULTI_T9: + case MXT_TOUCH_KEYARRAY_T15: + case MXT_TOUCH_PROXIMITY_T23: + case MXT_TOUCH_PROXKEY_T52: + case MXT_PROCI_GRIPFACE_T20: + case MXT_PROCG_NOISE_T22: + case MXT_PROCI_ONETOUCH_T24: + case MXT_PROCI_TWOTOUCH_T27: + case MXT_PROCI_GRIP_T40: + case MXT_PROCI_PALM_T41: + case MXT_PROCI_TOUCHSUPPRESSION_T42: + case MXT_PROCI_STYLUS_T47: + case MXT_PROCG_NOISESUPPRESSION_T48: + case MXT_PROCI_ADAPTIVETHRESHOLD_T55: + case MXT_PROCI_SHIELDLESS_T56: + case MXT_PROCI_EXTRATOUCHSCREENDATA_T57: + case MXT_PROCG_NOISESUPPRESSION_T62: + case MXT_SPT_COMMSCONFIG_T18: + case MXT_SPT_GPIOPWM_T19: + case MXT_SPT_SELFTEST_T25: + case MXT_SPT_CTECONFIG_T28: + case MXT_SPT_DIGITIZER_T43: + case MXT_SPT_CTECONFIG_T46: + case MXT_SPT_TIMER_T61: + return true; + default: + return false; + } +} + +static void mxt_dump_message(struct device *dev, + struct mxt_message *message) +{ + dev_dbg(dev, "reportid: %u\tmessage: %02x %02x %02x %02x %02x %02x %02x\n", + message->reportid, message->message[0], message->message[1], + message->message[2], message->message[3], message->message[4], + message->message[5], message->message[6]); +} + +/* + * Release all the fingers that are being tracked. To avoid unwanted gestures, + * move all the fingers to (0,0) with largest PRESSURE and TOUCH_MAJOR. + * Userspace apps can use these info to filter out these events and/or cancel + * existing gestures. + */ +static void mxt_release_all_fingers(struct mxt_data *data) +{ + struct device *dev = &data->client->dev; + struct input_dev *input_dev = data->input_dev; + int id; + bool need_update = false; + for (id = 0; id < MXT_MAX_FINGER; id++) { + if (data->current_id[id]) { + dev_warn(dev, "Move touch %d to (0,0)\n", id); + input_mt_slot(input_dev, id); + input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, + true); + input_report_abs(input_dev, ABS_MT_POSITION_X, 0); + input_report_abs(input_dev, ABS_MT_POSITION_Y, 0); + input_report_abs(input_dev, ABS_MT_PRESSURE, 255); + input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, 255); + need_update = true; + } + } + if (need_update) + input_sync(data->input_dev); + + for (id = 0; id < MXT_MAX_FINGER; id++) { + if (data->current_id[id]) { + dev_warn(dev, "Release touch contact %d\n", id); + input_mt_slot(input_dev, id); + input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, + false); + data->current_id[id] = false; + } + } + if (need_update) + input_sync(data->input_dev); +} + +static int mxt_wait_for_chg(struct mxt_data *data, unsigned int timeout_ms) +{ + struct device *dev = &data->client->dev; + struct completion *comp = &data->bl_completion; + unsigned long timeout = msecs_to_jiffies(timeout_ms); + long ret; + + ret = wait_for_completion_interruptible_timeout(comp, timeout); + if (ret < 0) { + dev_err(dev, "Wait for completion interrupted.\n"); + /* + * TODO: handle -EINTR better by terminating fw update process + * before returning to userspace by writing length 0x000 to + * device (iff we are in WAITING_FRAME_DATA state). + */ + return -EINTR; + } else if (ret == 0) { + dev_err(dev, "Wait for completion timed out.\n"); + return -ETIMEDOUT; + } + return 0; +} + +static int mxt_check_bootloader(struct mxt_data *data, unsigned int state) +{ + struct i2c_client *client = data->client; + int count; + u8 val; + +recheck: + if (state != MXT_WAITING_BOOTLOAD_CMD) { + /* + * In application update mode, the interrupt + * line signals state transitions. We must wait for the + * CHG assertion before reading the status byte. + * Once the status byte has been read, the line is deasserted. + */ + int ret = mxt_wait_for_chg(data, 300); + if (ret) { + dev_err(&client->dev, "Update wait error %d\n", ret); + return ret; + } + } + + count = i2c_master_recv(client, &val, 1); + if (count != 1) { + dev_err(&client->dev, "%s: i2c recv failed\n", __func__); + return count < 0 ? count : -EIO; + } + + switch (state) { + case MXT_WAITING_BOOTLOAD_CMD: + dev_info(&client->dev, "bootloader version: %d\n", + val & MXT_BOOT_STATUS_MASK); + case MXT_WAITING_FRAME_DATA: + val &= ~MXT_BOOT_STATUS_MASK; + break; + case MXT_FRAME_CRC_PASS: + if (val == MXT_FRAME_CRC_CHECK) + goto recheck; + break; + default: + return -EINVAL; + } + + if (val != state) { + dev_err(&client->dev, "Invalid bootloader mode state %d, %d\n", + val, state); + return -EINVAL; + } + + return 0; +} + +static int mxt_unlock_bootloader(struct i2c_client *client) +{ + int count; + u8 buf[2]; + + buf[0] = MXT_UNLOCK_CMD_LSB; + buf[1] = MXT_UNLOCK_CMD_MSB; + + count = i2c_master_send(client, buf, 2); + if (count != 2) { + dev_err(&client->dev, "%s: i2c send failed\n", __func__); + return count < 0 ? count : -EIO; + } + + return 0; +} + +static int mxt_fw_write(struct i2c_client *client, + const u8 *data, unsigned int frame_size) +{ + int count; + count = i2c_master_send(client, data, frame_size); + if (count != frame_size) { + dev_err(&client->dev, "%s: i2c send failed\n", __func__); + return count < 0 ? count : -EIO; + } + + return 0; +} + +#ifdef DEBUG +#define DUMP_LEN 16 +static void mxt_dump_xfer(struct device *dev, const char *func, u16 reg, + u16 len, const u8 *val) +{ + /* Rough guess for string size */ + char str[DUMP_LEN * 3 + 2]; + int i; + size_t n; + + for (i = 0, n = 0; i < len; i++) { + n += snprintf(&str[n], sizeof(str) - n, "%02x ", val[i]); + if ((i + 1) % DUMP_LEN == 0 || (i + 1) == len) { + dev_dbg(dev, + "%s(reg: %d len: %d offset: 0x%02x): %s\n", + func, reg, len, (i / DUMP_LEN) * DUMP_LEN, + str); + n = 0; + } + } +} +#undef DUMP_LEN +#else +static void mxt_dump_xfer(struct device *dev, const char *func, u16 reg, + u16 len, const u8 *val) { } +#endif + +static int mxt_read_reg(struct i2c_client *client, u16 reg, u16 len, void *val) +{ + struct i2c_msg xfer[2]; + int ret; + u8 buf[2]; + + buf[0] = reg & 0xff; + buf[1] = (reg >> 8) & 0xff; + + /* Write register */ + xfer[0].addr = client->addr; + xfer[0].flags = 0; + xfer[0].len = 2; + xfer[0].buf = buf; + + /* Read data */ + xfer[1].addr = client->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = len; + xfer[1].buf = val; + + ret = i2c_transfer(client->adapter, xfer, 2); + if (ret != 2) { + dev_err(&client->dev, "%s: i2c read failed\n", __func__); + return ret < 0 ? ret : -EIO; + } + + mxt_dump_xfer(&client->dev, __func__, reg, len, val); + + return 0; +} + +static int mxt_write_reg(struct i2c_client *client, u16 reg, u16 len, + const void *val) +{ + size_t count = 2 + len; /* + 2-byte offset */ + int ret; + u8 buf[count]; + + buf[0] = reg & 0xff; + buf[1] = (reg >> 8) & 0xff; + memcpy(&buf[2], val, len); + + mxt_dump_xfer(&client->dev, __func__, reg, len, val); + + ret = i2c_master_send(client, buf, count); + if (ret != count) { + dev_err(&client->dev, "%s: i2c write failed\n", __func__); + return ret < 0 ? ret : -EIO; + } + + return 0; +} + +static struct mxt_object *mxt_get_object(struct mxt_data *data, u8 type) +{ + struct mxt_object *object; + int i; + + for (i = 0; i < data->info.object_num; i++) { + object = data->object_table + i; + if (object->type == type) + return object; + } + + dev_err(&data->client->dev, "Invalid object type\n"); + return NULL; +} + +static int mxt_read_object(struct mxt_data *data, struct mxt_object *object, + u8 instance, void *val) +{ + u16 addr; + + BUG_ON(instance >= object->instances); + addr = object->start_address + instance * object->size; + return mxt_read_reg(data->client, addr, object->size, val); +} + +static int mxt_write_object(struct mxt_data *data, u8 type, u8 instance, + u8 offset, u8 val) +{ + struct mxt_object *object; + u16 reg; + + object = mxt_get_object(data, type); + if (!object || instance >= object->instances || offset >= object->size) + return -EINVAL; + + reg = object->start_address + instance * object->size + offset; + return mxt_write_reg(data->client, reg, 1, &val); +} + +static int mxt_read_num_messages(struct mxt_data *data, u8 *count) +{ + /* TODO: Optimization: read first message along with message count */ + return mxt_read_reg(data->client, data->T44_address, 1, count); +} + +static int mxt_read_messages(struct mxt_data *data, u8 count, + struct mxt_message *messages) +{ + return mxt_read_reg(data->client, data->T5_address, + sizeof(struct mxt_message) * count, messages); +} + +static void mxt_input_button(struct mxt_data *data, struct mxt_message *message) +{ + struct device *dev = &data->client->dev; + struct input_dev *input = data->input_dev; + bool button; + + /* Active-low switch */ + button = !(message->message[0] & MXT_GPIO3_MASK); + input_report_key(input, BTN_LEFT, button); + dev_dbg(dev, "Button state: %d\n", button); +} + +/* + * Assume a circle touch contact and use the diameter as the touch major. + * touch_pixels = touch_channels * (max_area_pixels / max_area_channels) + * touch_pixels = pi * (touch_major / 2) ^ 2; + */ +static int get_touch_major_pixels(struct mxt_data *data, int touch_channels) +{ + int touch_pixels; + + if (data->max_area_channels == 0) + return 0; + + touch_pixels = DIV_ROUND_CLOSEST(touch_channels * data->max_area_pixels, + data->max_area_channels); + return int_sqrt(DIV_ROUND_CLOSEST(touch_pixels * 100, 314)) * 2; +} + +static void mxt_input_touch(struct mxt_data *data, struct mxt_message *message) +{ + struct device *dev = &data->client->dev; + struct input_dev *input_dev = data->input_dev; + u8 status; + int x; + int y; + int area; + int amplitude; + int vector1, vector2; + int id; + int touch_major; + + id = message->reportid - data->T9_reportid_min; + + status = message->message[0]; + x = (message->message[1] << 4) | ((message->message[3] >> 4) & 0xf); + y = (message->message[2] << 4) | ((message->message[3] & 0xf)); + if (data->max_x < 1024) + x >>= 2; + if (data->max_y < 1024) + y >>= 2; + + area = message->message[4]; + touch_major = get_touch_major_pixels(data, area); + amplitude = message->message[5]; + + /* The two vector components are 4-bit signed ints (2s complement) */ + vector1 = (signed)((signed char)message->message[6]) >> 4; + vector2 = (signed)((signed char)(message->message[6] << 4)) >> 4; + + dev_dbg(dev, + "[%d] %c%c%c%c%c%c%c%c x: %d y: %d area: %d amp: %d vector: [%d,%d]\n", + id, + (status & MXT_DETECT) ? 'D' : '.', + (status & MXT_PRESS) ? 'P' : '.', + (status & MXT_RELEASE) ? 'R' : '.', + (status & MXT_MOVE) ? 'M' : '.', + (status & MXT_VECTOR) ? 'V' : '.', + (status & MXT_AMP) ? 'A' : '.', + (status & MXT_SUPPRESS) ? 'S' : '.', + (status & MXT_UNGRIP) ? 'U' : '.', + x, y, area, amplitude, vector1, vector2); + + input_mt_slot(input_dev, id); + input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, + status & MXT_DETECT); + data->current_id[id] = status & MXT_DETECT; + + if (status & MXT_DETECT) { + input_report_abs(input_dev, ABS_MT_POSITION_X, x); + input_report_abs(input_dev, ABS_MT_POSITION_Y, y); + input_report_abs(input_dev, ABS_MT_PRESSURE, amplitude); + input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, touch_major); + /* TODO: Use vector to report ORIENTATION & TOUCH_MINOR */ + } +} + +static int mxt_proc_messages(struct mxt_data *data, u8 count, bool report) +{ + struct device *dev = &data->client->dev; + struct mxt_message messages[count], *msg; + int ret; + bool update_input; + + ret = mxt_read_messages(data, count, messages); + if (ret) { + dev_err(dev, "Failed to read %u messages (%d).\n", count, ret); + return ret; + } + if (!report) + return 0; + + update_input = false; + for (msg = messages; msg < &messages[count]; msg++) { + mxt_dump_message(dev, msg); + + if (msg->reportid >= data->T9_reportid_min && + msg->reportid <= data->T9_reportid_max) { + mxt_input_touch(data, msg); + update_input = true; + } else if (msg->reportid == data->T19_reportid) { + mxt_input_button(data, msg); + update_input = true; + } else if (msg->reportid == data->T6_reportid) { + data->config_csum = msg->message[1] | + (msg->message[2] << 8) | + (msg->message[3] << 16); + dev_info(dev, "Status: %02x Config Checksum: %06x\n", + msg->message[0], data->config_csum); + if (msg->message[0] == 0x00) + complete(&data->auto_cal_completion); + } + } + + if (update_input) { + input_mt_report_pointer_emulation(data->input_dev, + data->is_tp); + input_sync(data->input_dev); + } + + return 0; +} + +static int mxt_handle_messages(struct mxt_data *data, bool report) +{ + struct device *dev = &data->client->dev; + int ret; + u8 count; + + ret = mxt_read_num_messages(data, &count); + if (ret) { + dev_err(dev, "Failed to read message count (%d).\n", ret); + return ret; + } + + if (count > 0) + ret = mxt_proc_messages(data, count, report); + + return ret; +} + +static bool mxt_in_bootloader(struct mxt_data *data) +{ + struct i2c_client *client = data->client; + return (client->addr == MXT_BOOT_LOW || client->addr == MXT_BOOT_HIGH); +} + +static int mxt_enter_bl(struct mxt_data *data) +{ + struct i2c_client *client = data->client; + struct device *dev = &client->dev; + int ret; + + if (mxt_in_bootloader(data)) + return 0; + + disable_irq(data->irq); + + if (data->input_dev) { + input_unregister_device(data->input_dev); + data->input_dev = NULL; + } + + /* Change to the bootloader mode */ + ret = mxt_write_object(data, MXT_GEN_COMMAND_T6, 0, + MXT_COMMAND_RESET, MXT_BOOT_VALUE); + if (ret) { + enable_irq(data->irq); + return ret; + } + + /* Change to slave address of bootloader */ + if (client->addr == MXT_APP_LOW) + client->addr = MXT_BOOT_LOW; + else + client->addr = MXT_BOOT_HIGH; + + INIT_COMPLETION(data->bl_completion); + enable_irq(data->irq); + + /* Wait for CHG assert to indicate successful reset into bootloader */ + ret = mxt_wait_for_chg(data, MXT_RESET_TIME); + if (ret) { + dev_err(dev, "Failed waiting for reset to bootloader.\n"); + if (client->addr == MXT_BOOT_LOW) + client->addr = MXT_APP_LOW; + else + client->addr = MXT_APP_HIGH; + return ret; + } + return 0; +} + +static void mxt_exit_bl(struct mxt_data *data) +{ + struct i2c_client *client = data->client; + struct device *dev = &client->dev; + int error; + + if (!mxt_in_bootloader(data)) + return; + + /* Wait for reset */ + mxt_wait_for_chg(data, MXT_FWRESET_TIME); + + disable_irq(data->irq); + if (client->addr == MXT_BOOT_LOW) + client->addr = MXT_APP_LOW; + else + client->addr = MXT_APP_HIGH; + + kfree(data->object_table); + data->object_table = NULL; + + error = mxt_initialize(data); + if (error) { + dev_err(dev, "Failed to initialize on exit bl. error = %d\n", + error); + return; + } + + error = mxt_input_dev_create(data); + if (error) { + dev_err(dev, "Create input dev failed after init. error = %d\n", + error); + return; + } + + error = mxt_handle_messages(data, false); + if (error) + dev_err(dev, "Handle messages failed after init. error = %d\n", + error); + enable_irq(data->irq); +} + +static irqreturn_t mxt_interrupt(int irq, void *dev_id) +{ + struct mxt_data *data = (struct mxt_data *)dev_id; + + if (mxt_in_bootloader(data)) { + /* bootloader state transition completion */ + complete(&data->bl_completion); + } else { + mxt_handle_messages(data, true); + } + return IRQ_HANDLED; +} + +static int mxt_apply_pdata_config(struct mxt_data *data) +{ + const struct mxt_platform_data *pdata = data->pdata; + struct device *dev = &data->client->dev; + int i, offset; + int ret; + + if (!pdata->config) { + dev_info(dev, "No cfg data defined, skipping reg init\n"); + return 0; + } + + for (offset = 0, i = 0; i < data->info.object_num; i++) { + struct mxt_object *object = &data->object_table[i]; + size_t config_size; + + if (!mxt_object_writable(object->type)) + continue; + + config_size = object->size * object->instances; + if (offset + config_size > pdata->config_length) { + dev_err(dev, "Not enough config data!\n"); + return -EINVAL; + } + + ret = mxt_write_reg(data->client, object->start_address, + config_size, &pdata->config[offset]); + if (ret) + return ret; + offset += config_size; + } + + return 0; +} + +static int mxt_handle_pdata(struct mxt_data *data) +{ + const struct mxt_platform_data *pdata = data->pdata; + struct device *dev = &data->client->dev; + u8 voltage; + int ret; + + if (!pdata) { + dev_info(dev, "No platform data provided\n"); + return 0; + } + + ret = mxt_apply_pdata_config(data); + if (ret) + return ret; + + /* Set touchscreen lines */ + mxt_write_object(data, MXT_TOUCH_MULTI_T9, 0, + MXT_TOUCH_XSIZE, pdata->x_line); + mxt_write_object(data, MXT_TOUCH_MULTI_T9, 0, + MXT_TOUCH_YSIZE, pdata->y_line); + + /* Set touchscreen orient */ + mxt_write_object(data, MXT_TOUCH_MULTI_T9, 0, + MXT_TOUCH_ORIENT, pdata->orient); + + /* Set touchscreen burst length */ + mxt_write_object(data, MXT_TOUCH_MULTI_T9, 0, + MXT_TOUCH_BLEN, pdata->blen); + + /* Set touchscreen threshold */ + mxt_write_object(data, MXT_TOUCH_MULTI_T9, 0, + MXT_TOUCH_TCHTHR, pdata->threshold); + + /* Set touchscreen resolution */ + mxt_write_object(data, MXT_TOUCH_MULTI_T9, 0, + MXT_TOUCH_XRANGE_LSB, (pdata->x_size - 1) & 0xff); + mxt_write_object(data, MXT_TOUCH_MULTI_T9, 0, + MXT_TOUCH_XRANGE_MSB, (pdata->x_size - 1) >> 8); + mxt_write_object(data, MXT_TOUCH_MULTI_T9, 0, + MXT_TOUCH_YRANGE_LSB, (pdata->y_size - 1) & 0xff); + mxt_write_object(data, MXT_TOUCH_MULTI_T9, 0, + MXT_TOUCH_YRANGE_MSB, (pdata->y_size - 1) >> 8); + + /* Set touchscreen voltage */ + if (pdata->voltage) { + if (pdata->voltage < MXT_VOLTAGE_DEFAULT) { + voltage = (MXT_VOLTAGE_DEFAULT - pdata->voltage) / + MXT_VOLTAGE_STEP; + voltage = 0xff - voltage + 1; + } else + voltage = (pdata->voltage - MXT_VOLTAGE_DEFAULT) / + MXT_VOLTAGE_STEP; + + mxt_write_object(data, MXT_SPT_CTECONFIG_T28, 0, + MXT_CTE_VOLTAGE, voltage); + } + + /* Backup to memory */ + ret = mxt_write_object(data, MXT_GEN_COMMAND_T6, 0, + MXT_COMMAND_BACKUPNV, MXT_BACKUP_VALUE); + if (ret) + return ret; + msleep(MXT_BACKUP_TIME); + + return 0; +} + +/* Update 24-bit CRC with two new bytes of data */ +static u32 crc24_step(u32 crc, u8 byte1, u8 byte2) +{ + const u32 crcpoly = 0x80001b; + u16 data = byte1 | (byte2 << 8); + u32 result = data ^ (crc << 1); + + /* XOR result with crcpoly if bit 25 is set (overflow occurred) */ + if (result & 0x01000000) + result ^= crcpoly; + + return result & 0x00ffffff; +} + +static u32 crc24(u32 crc, const u8 *data, size_t len) +{ + size_t i; + + for (i = 0; i < len - 1; i += 2) + crc = crc24_step(crc, data[i], data[i + 1]); + + /* If there were an odd number of bytes pad with 0 */ + if (i < len) + crc = crc24_step(crc, data[i], 0); + + return crc; +} + +static int mxt_verify_info_block_csum(struct mxt_data *data, const void *buf) +{ + struct i2c_client *client = data->client; + struct device *dev = &client->dev; + size_t object_table_size, info_block_size; + u32 crc = 0; + u8 *info_block; + int ret = 0; + + object_table_size = data->info.object_num * MXT_OBJECT_SIZE; + info_block_size = sizeof(data->info) + object_table_size; + info_block = kmalloc(info_block_size, GFP_KERNEL); + if (!info_block) + return -ENOMEM; + + /* + * Information Block CRC is computed over both ID info and Object Table + * So concat them in a temporary buffer, before computing CRC. + * TODO: refactor how the info block is read from the device such + * that it ends up in a single buffer and this copy is not needed. + */ + memcpy(info_block, &data->info, sizeof(data->info)); + memcpy(&info_block[sizeof(data->info)], buf, object_table_size); + + crc = crc24(crc, info_block, info_block_size); + + if (crc != data->info_csum) { + dev_err(dev, "Information Block CRC mismatch: %06x != %06x\n", + data->info_csum, crc); + ret = -EINVAL; + } + + kfree(info_block); + return ret; +} + +static int mxt_get_object_table(struct mxt_data *data) +{ + struct i2c_client *client = data->client; + struct device *dev = &client->dev; + int error; + int i; + u8 reportid; + u8 buf[data->info.object_num][MXT_OBJECT_SIZE]; + u8 csum[3]; + + data->object_table = kcalloc(data->info.object_num, + sizeof(struct mxt_object), GFP_KERNEL); + if (!data->object_table) { + dev_err(dev, "Failed to allocate object table\n"); + return -ENOMEM; + } + + error = mxt_read_reg(client, MXT_OBJECT_START, sizeof(buf), buf); + if (error) + return error; + + /* + * Read Information Block checksum from 3 bytes immediately following + * info block + */ + error = mxt_read_reg(client, MXT_OBJECT_START + sizeof(buf), + sizeof(csum), csum); + if (error) + return error; + + data->info_csum = csum[0] | (csum[1] << 8) | (csum[2] << 16); + dev_info(dev, "Information Block Checksum = %06x\n", data->info_csum); + + error = mxt_verify_info_block_csum(data, buf); + if (error) + return error; + + /* Valid Report IDs start counting from 1 */ + reportid = 1; + for (i = 0; i < data->info.object_num; i++) { + struct mxt_object *object = &data->object_table[i]; + u8 num_ids, min_id, max_id; + + object->type = buf[i][0]; + object->start_address = (buf[i][2] << 8) | buf[i][1]; + object->size = buf[i][3] + 1; + object->instances = buf[i][4] + 1; + object->num_report_ids = buf[i][5]; + + num_ids = object->num_report_ids * object->instances; + min_id = num_ids ? reportid : 0; + max_id = num_ids ? reportid + num_ids - 1 : 0; + reportid += num_ids; + + dev_info(dev, + "Type %2d Start %3d Size %3d Instances %2d ReportIDs %3u : %3u\n", + object->type, object->start_address, object->size, + object->instances, min_id, max_id); + + /* Save data for objects used when processing interrupts */ + switch (object->type) { + case MXT_GEN_MESSAGE_T5: + data->T5_address = object->start_address; + break; + case MXT_GEN_COMMAND_T6: + data->T6_reportid = min_id; + break; + case MXT_TOUCH_MULTI_T9: + data->T9_reportid_min = min_id; + data->T9_reportid_max = max_id; + break; + case MXT_SPT_GPIOPWM_T19: + data->T19_reportid = min_id; + break; + case MXT_SPT_MESSAGECOUNT_T44: + data->T44_address = object->start_address; + break; + } + } + + return 0; +} + +static int mxt_calc_resolution(struct mxt_data *data) +{ + struct i2c_client *client = data->client; + u8 orient; + __le16 xyrange[2]; + unsigned int max_x, max_y; + u8 xylines[2]; + int ret; + + struct mxt_object *T9 = mxt_get_object(data, MXT_TOUCH_MULTI_T9); + if (T9 == NULL) + return -EINVAL; + + /* Get touchscreen resolution */ + ret = mxt_read_reg(client, T9->start_address + MXT_TOUCH_XRANGE_LSB, + 4, xyrange); + if (ret) + return ret; + + ret = mxt_read_reg(client, T9->start_address + MXT_TOUCH_ORIENT, + 1, &orient); + if (ret) + return ret; + + ret = mxt_read_reg(client, T9->start_address + MXT_TOUCH_XSIZE, + 2, xylines); + if (ret) + return ret; + + max_x = le16_to_cpu(xyrange[0]); + max_y = le16_to_cpu(xyrange[1]); + + if (orient & MXT_XY_SWITCH) { + data->max_x = max_y; + data->max_y = max_x; + } else { + data->max_x = max_x; + data->max_y = max_y; + } + + data->max_area_pixels = max_x * max_y; + data->max_area_channels = xylines[0] * xylines[1]; + + return 0; +} + +/* + * Atmel Raw Config File Format + * + * The first four lines of the raw config file contain: + * 1) Version + * 2) Chip ID Information (first 7 bytes of device memory) + * 3) Chip Information Block 24-bit CRC Checksum + * 4) Chip Configuration 24-bit CRC Checksum + * + * The rest of the file consists of one line per object instance: + * <TYPE> <INSTANCE> <SIZE> <CONTENTS> + * + * <TYPE> - 2-byte object type as hex + * <INSTANCE> - 2-byte object instance number as hex + * <SIZE> - 2-byte object size as hex + * <CONTENTS> - array of <SIZE> 1-byte hex values + */ +static int mxt_cfg_verify_hdr(struct mxt_data *data, char **config) +{ + struct i2c_client *client = data->client; + struct device *dev = &client->dev; + struct mxt_info info; + char *token; + int ret = 0; + u32 crc; + + /* Process the first four lines of the file*/ + /* 1) Version */ + token = strsep(config, "\n"); + dev_info(dev, "Config File: Version = %s\n", token ?: "<null>"); + if (!token || + strncmp(token, MXT_CONFIG_VERSION, strlen(MXT_CONFIG_VERSION))) { + dev_err(dev, "Invalid config file: Bad Version\n"); + return -EINVAL; + } + + /* 2) Chip ID */ + token = strsep(config, "\n"); + if (!token) { + dev_err(dev, "Invalid config file: No Chip ID\n"); + return -EINVAL; + } + ret = sscanf(token, "%hhx %hhx %hhx %hhx %hhx %hhx %hhx", + &info.family_id, &info.variant_id, + &info.version, &info.build, &info.matrix_xsize, + &info.matrix_ysize, &info.object_num); + dev_info(dev, "Config File: Chip ID = %02x %02x %02x %02x %02x %02x %02x\n", + info.family_id, info.variant_id, info.version, info.build, + info.matrix_xsize, info.matrix_ysize, info.object_num); + if (ret != 7 || + info.family_id != data->info.family_id || + info.variant_id != data->info.variant_id || + info.version != data->info.version || + info.build != data->info.build || + info.matrix_xsize != data->info.matrix_xsize || + info.matrix_ysize != data->info.matrix_ysize || + info.object_num != data->info.object_num) { + dev_err(dev, "Invalid config file: Chip ID info mismatch\n"); + dev_err(dev, "Chip Info: %02x %02x %02x %02x %02x %02x %02x\n", + data->info.family_id, data->info.variant_id, + data->info.version, data->info.build, + data->info.matrix_xsize, data->info.matrix_ysize, + data->info.object_num); + return -EINVAL; + } + + /* 3) Info Block CRC */ + token = strsep(config, "\n"); + if (!token) { + dev_err(dev, "Invalid config file: No Info Block CRC\n"); + return -EINVAL; + } + ret = sscanf(token, "%x", &crc); + dev_info(dev, "Config File: Info Block CRC = %06x\n", crc); + if (ret != 1 || crc != data->info_csum) { + dev_err(dev, "Invalid config file: Bad Info Block CRC\n"); + return -EINVAL; + } + + /* 4) Config CRC */ + /* + * Parse but don't verify against current config; + * TODO: Verify against CRC of rest of file? + */ + token = strsep(config, "\n"); + if (!token) { + dev_err(dev, "Invalid config file: No Config CRC\n"); + return -EINVAL; + } + ret = sscanf(token, "%x", &crc); + dev_info(dev, "Config File: Config CRC = %06x\n", crc); + if (ret != 1) { + dev_err(dev, "Invalid config file: Bad Config CRC\n"); + return -EINVAL; + } + + return 0; +} + +static int mxt_cfg_proc_line(struct mxt_data *data, const char *line, + struct list_head *cfg_list) +{ + int ret; + u16 type, instance, size; + int len; + struct mxt_cfg_file_line *cfg_line; + struct mxt_object *object; + u8 *content; + size_t i; + + ret = sscanf(line, "%hx %hx %hx%n", &type, &instance, &size, &len); + /* Skip unparseable lines */ + if (ret < 3) + return 0; + /* Only support 1-byte types */ + if (type > 0xff) + return -EINVAL; + + /* Supplied object MUST be a valid instance and match object size */ + object = mxt_get_object(data, type); + if (!object || instance > object->instances || size != object->size) + return -EINVAL; + + content = kmalloc(size, GFP_KERNEL); + if (!content) + return -ENOMEM; + + for (i = 0; i < size; i++) { + line += len; + ret = sscanf(line, "%hhx%n", &content[i], &len); + if (ret < 1) { + ret = -EINVAL; + goto free_content; + } + } + + cfg_line = kzalloc(sizeof(*cfg_line), GFP_KERNEL); + if (!cfg_line) { + ret = -ENOMEM; + goto free_content; + } + INIT_LIST_HEAD(&cfg_line->list); + cfg_line->addr = object->start_address + instance * object->size; + cfg_line->size = object->size; + cfg_line->content = content; + list_add_tail(&cfg_line->list, cfg_list); + + return 0; + +free_content: + kfree(content); + return ret; +} + +static int mxt_cfg_proc_data(struct mxt_data *data, char **config) +{ + struct i2c_client *client = data->client; + struct device *dev = &client->dev; + char *line; + int ret = 0; + struct list_head cfg_lines; + struct mxt_cfg_file_line *cfg_line, *cfg_line_tmp; + + INIT_LIST_HEAD(&cfg_lines); + + while ((line = strsep(config, "\n"))) { + ret = mxt_cfg_proc_line(data, line, &cfg_lines); + if (ret < 0) + goto free_objects; + } + + list_for_each_entry(cfg_line, &cfg_lines, list) { + dev_dbg(dev, "Addr = %u Size = %u\n", + cfg_line->addr, cfg_line->size); + print_hex_dump(KERN_DEBUG, "atmel_mxt_ts: ", DUMP_PREFIX_OFFSET, + 16, 1, cfg_line->content, cfg_line->size, false); + + ret = mxt_write_reg(client, cfg_line->addr, cfg_line->size, + cfg_line->content); + if (ret) + break; + } + +free_objects: + list_for_each_entry_safe(cfg_line, cfg_line_tmp, &cfg_lines, list) { + list_del(&cfg_line->list); + kfree(cfg_line->content); + kfree(cfg_line); + } + return ret; +} + +static int mxt_load_config(struct mxt_data *data, const char *fn) +{ + struct i2c_client *client = data->client; + struct device *dev = &client->dev; + const struct firmware *fw = NULL; + int ret, ret2; + char *cfg_copy = NULL; + char *running; + + ret = request_firmware(&fw, fn, dev); + if (ret) { + dev_err(dev, "Unable to open config file %s\n", fn); + return ret; + } + + dev_info(dev, "Using config file %s (size = %zu)\n", fn, fw->size); + + /* Make a mutable, '\0'-terminated copy of the config file */ + cfg_copy = kmalloc(fw->size + 1, GFP_KERNEL); + if (!cfg_copy) { + ret = -ENOMEM; + goto err_alloc_copy; + } + memcpy(cfg_copy, fw->data, fw->size); + cfg_copy[fw->size] = '\0'; + + /* Verify config file header (after which running points to data) */ + running = cfg_copy; + ret = mxt_cfg_verify_hdr(data, &running); + if (ret) { + dev_err(dev, "Error verifying config header (%d)\n", ret); + goto free_cfg_copy; + } + + disable_irq(data->irq); + if (data->input_dev) { + input_unregister_device(data->input_dev); + data->input_dev = NULL; + } + + /* Write configuration */ + ret = mxt_cfg_proc_data(data, &running); + if (ret) { + dev_err(dev, "Error writing config file (%d)\n", ret); + goto register_input_dev; + } + /* Backup nvram */ + ret = mxt_write_object(data, MXT_GEN_COMMAND_T6, 0, + MXT_COMMAND_BACKUPNV, + MXT_BACKUP_VALUE); + if (ret) { + dev_err(dev, "Error backup to nvram (%d)\n", ret); + goto register_input_dev; + } + msleep(MXT_BACKUP_TIME); + + /* Reset device */ + ret = mxt_write_object(data, MXT_GEN_COMMAND_T6, 0, + MXT_COMMAND_RESET, 1); + if (ret) { + dev_err(dev, "Error resetting device (%d)\n", ret); + goto register_input_dev; + } + msleep(MXT_RESET_TIME); + +register_input_dev: + ret2 = mxt_input_dev_create(data); + if (ret2) { + dev_err(dev, "Error creating input_dev (%d)\n", ret2); + ret = ret2; + } + enable_irq(data->irq); + + /* Clear message buffer */ + ret2 = mxt_handle_messages(data, true); + if (ret2) { + dev_err(dev, "Error clearing msg buffer (%d)\n", ret2); + ret = ret2; + } +free_cfg_copy: + kfree(cfg_copy); +err_alloc_copy: + release_firmware(fw); + return ret; +} + +static int mxt_load_fw(struct device *dev, const char *fn) +{ + struct mxt_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + const struct firmware *fw = NULL; + unsigned int frame_size; + unsigned int pos = 0; + int ret; + + ret = request_firmware(&fw, fn, dev); + if (ret) { + dev_err(dev, "Unable to open firmware %s\n", fn); + return ret; + } + + if (!mxt_in_bootloader(data)) { + ret = mxt_enter_bl(data); + if (ret) { + dev_err(dev, "Failed to reset to bootloader.\n"); + goto out; + } + } + + ret = mxt_check_bootloader(data, MXT_WAITING_BOOTLOAD_CMD); + if (ret) + goto out; + + /* Unlock bootloader */ + ret = mxt_unlock_bootloader(client); + if (ret) + goto out; + + while (pos < fw->size) { + ret = mxt_check_bootloader(data, MXT_WAITING_FRAME_DATA); + if (ret) + goto out; + + frame_size = (fw->data[pos] << 8) + fw->data[pos + 1]; + + /* We should add 2 at frame size as the the firmware data is not + * included the CRC bytes. + */ + frame_size += 2; + + /* Write one frame to device */ + ret = mxt_fw_write(client, fw->data + pos, frame_size); + if (ret) + goto out; + + ret = mxt_check_bootloader(data, MXT_FRAME_CRC_PASS); + if (ret) + goto out; + + pos += frame_size; + dev_dbg(dev, "Updated %d bytes / %zd bytes\n", pos, fw->size); + } + + /* Device exits bl mode to app mode only if successful */ + mxt_exit_bl(data); +out: + release_firmware(fw); + return ret; +} + +/* + * Helper function for performing a T6 diagnostic command + */ +static int mxt_T6_diag_cmd(struct mxt_data *data, struct mxt_object *T6, + u8 cmd) +{ + int ret; + u16 addr = T6->start_address + MXT_COMMAND_DIAGNOSTIC; + + ret = mxt_write_reg(data->client, addr, 1, &cmd); + if (ret) + return ret; + + /* + * Poll T6.diag until it returns 0x00, which indicates command has + * completed. + */ + while (cmd != 0) { + ret = mxt_read_reg(data->client, addr, 1, &cmd); + if (ret) + return ret; + } + return 0; +} + +/* + * SysFS Helper function for reading DELTAS and REFERENCE values for T37 object + * + * For both modes, a T37_buf is allocated to stores matrix_xsize * matrix_ysize + * 2-byte (little-endian) values, which are returned to userspace unmodified. + * + * It is left to userspace to parse the 2-byte values. + * - deltas are signed 2's complement 2-byte little-endian values. + * s32 delta = (b[0] + (b[1] << 8)); + * - refs are signed 'offset binary' 2-byte little-endian values, with offset + * value 0x4000: + * s32 ref = (b[0] + (b[1] << 8)) - 0x4000; + */ +static ssize_t mxt_T37_fetch(struct mxt_data *data, u8 mode) +{ + struct mxt_object *T6, *T37; + u8 *obuf; + ssize_t ret = 0; + size_t i; + size_t T37_buf_size, num_pages; + size_t pos; + + if (!data || !data->object_table) + return -ENODEV; + + T6 = mxt_get_object(data, MXT_GEN_COMMAND_T6); + T37 = mxt_get_object(data, MXT_DEBUG_DIAGNOSTIC_T37); + if (!T6 || T6->size < 6 || !T37 || T37->size < 3) { + dev_err(&data->client->dev, "Invalid T6 or T37 object\n"); + return -ENODEV; + } + + /* Something has gone wrong if T37_buf is already allocated */ + if (data->T37_buf) + return -EINVAL; + + T37_buf_size = data->info.matrix_xsize * data->info.matrix_ysize * + sizeof(__le16); + data->T37_buf_size = T37_buf_size; + data->T37_buf = kmalloc(data->T37_buf_size, GFP_KERNEL); + if (!data->T37_buf) + return -ENOMEM; + + /* Temporary buffer used to fetch one T37 page */ + obuf = kmalloc(T37->size, GFP_KERNEL); + if (!obuf) + return -ENOMEM; + + disable_irq(data->irq); + num_pages = DIV_ROUND_UP(T37_buf_size, T37->size - 2); + pos = 0; + for (i = 0; i < num_pages; i++) { + u8 cmd; + size_t chunk_len; + + /* For first page, send mode as cmd, otherwise PageUp */ + cmd = (i == 0) ? mode : MXT_T6_CMD_PAGE_UP; + ret = mxt_T6_diag_cmd(data, T6, cmd); + if (ret) + goto err_free_T37_buf; + + ret = mxt_read_object(data, T37, 0, obuf); + if (ret) + goto err_free_T37_buf; + + /* Verify first two bytes are current mode and page # */ + if (obuf[0] != mode) { + dev_err(&data->client->dev, + "Unexpected mode (%u != %u)\n", obuf[0], mode); + ret = -EIO; + goto err_free_T37_buf; + } + + if (obuf[1] != i) { + dev_err(&data->client->dev, + "Unexpected page (%u != %zu)\n", obuf[1], i); + ret = -EIO; + goto err_free_T37_buf; + } + + /* + * Copy the data portion of the page, or however many bytes are + * left, whichever is less. + */ + chunk_len = min((size_t)T37->size - 2, T37_buf_size - pos); + memcpy(&data->T37_buf[pos], &obuf[2], chunk_len); + pos += chunk_len; + } + + goto out; + +err_free_T37_buf: + kfree(data->T37_buf); + data->T37_buf = NULL; + data->T37_buf_size = 0; +out: + kfree(obuf); + enable_irq(data->irq); + return ret ?: 0; +} + +/* + ************************************************************** + * sysfs interface + ************************************************************** +*/ +static ssize_t mxt_backupnv_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mxt_data *data = dev_get_drvdata(dev); + int ret; + + /* Backup non-volatile memory */ + ret = mxt_write_object(data, MXT_GEN_COMMAND_T6, 0, + MXT_COMMAND_BACKUPNV, MXT_BACKUP_VALUE); + if (ret) + return ret; + msleep(MXT_BACKUP_TIME); + + return count; +} + +static ssize_t mxt_calibrate_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mxt_data *data = dev_get_drvdata(dev); + int ret; + + disable_irq(data->irq); + + /* Perform touch surface recalibration */ + ret = mxt_write_object(data, MXT_GEN_COMMAND_T6, 0, + MXT_COMMAND_CALIBRATE, 1); + if (ret) + return ret; + msleep(MXT_CAL_TIME); + + enable_irq(data->irq); + + return count; +} + +static ssize_t mxt_config_csum_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxt_data *data = dev_get_drvdata(dev); + return scnprintf(buf, PAGE_SIZE, "%06x\n", data->config_csum); +} + +static ssize_t mxt_fw_file_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxt_data *data = dev_get_drvdata(dev); + return scnprintf(buf, PAGE_SIZE, "%s\n", data->fw_file); +} + +static int mxt_update_file_name(struct device *dev, char** file_name, + const char *buf, size_t count) +{ + char *file_name_tmp; + + /* Simple sanity check */ + if (count > 64) { + dev_warn(dev, "File name too long\n"); + return -EINVAL; + } + + file_name_tmp = krealloc(*file_name, count + 1, GFP_KERNEL); + if (!file_name_tmp) { + dev_warn(dev, "no memory\n"); + return -ENOMEM; + } + + *file_name = file_name_tmp; + memcpy(*file_name, buf, count); + + /* Echo into the sysfs entry may append newline at the end of buf */ + if (buf[count - 1] == '\n') + (*file_name)[count - 1] = '\0'; + else + (*file_name)[count] = '\0'; + + return 0; +} + +static ssize_t mxt_fw_file_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mxt_data *data = dev_get_drvdata(dev); + int ret; + + ret = mxt_update_file_name(dev, &data->fw_file, buf, count); + if (ret) + return ret; + + return count; +} + +static ssize_t mxt_config_file_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxt_data *data = dev_get_drvdata(dev); + return scnprintf(buf, PAGE_SIZE, "%s\n", data->config_file); +} + +static ssize_t mxt_config_file_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mxt_data *data = dev_get_drvdata(dev); + int ret; + + ret = mxt_update_file_name(dev, &data->config_file, buf, count); + return ret ? ret : count; +} + +static ssize_t mxt_fw_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxt_data *data = dev_get_drvdata(dev); + struct mxt_info *info = &data->info; + return scnprintf(buf, PAGE_SIZE, "%d.%d.%d\n", + info->version >> 4, info->version & 0xf, info->build); +} + +/* Hardware Version is <FamilyID>.<VariantID> */ +static ssize_t mxt_hw_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxt_data *data = dev_get_drvdata(dev); + struct mxt_info *info = &data->info; + return scnprintf(buf, PAGE_SIZE, "%d.%d\n", + info->family_id, info->variant_id); +} + +static ssize_t mxt_info_csum_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxt_data *data = dev_get_drvdata(dev); + return scnprintf(buf, PAGE_SIZE, "%06x\n", data->info_csum); +} + +/* Matrix Size is <MatrixSizeX> <MatrixSizeY> */ +static ssize_t mxt_matrix_size_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxt_data *data = dev_get_drvdata(dev); + struct mxt_info *info = &data->info; + return scnprintf(buf, PAGE_SIZE, "%u %u\n", + info->matrix_xsize, info->matrix_ysize); +} + +static ssize_t mxt_object_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mxt_data *data = dev_get_drvdata(dev); + int ret; + u32 param; + u8 type, instance, offset, val; + + ret = kstrtou32(buf, 16, ¶m); + if (ret < 0) + return -EINVAL; + + /* + * Byte Write Command is encoded in 32-bit word: TTIIOOVV: + * <Type> <Instance> <Offset> <Value> + */ + type = (param & 0xff000000) >> 24; + instance = (param & 0x00ff0000) >> 16; + offset = (param & 0x0000ff00) >> 8; + val = param & 0x000000ff; + + ret = mxt_write_object(data, type, instance, offset, val); + if (ret) + return ret; + + return count; +} + +static ssize_t mxt_update_config_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mxt_data *data = dev_get_drvdata(dev); + int error; + + error = mxt_load_config(data, data->config_file); + if (error) + dev_err(dev, "The config update failed (%d)\n", error); + else + dev_dbg(dev, "The config update succeeded\n"); + + return error ?: count; +} + +static ssize_t mxt_update_fw_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mxt_data *data = dev_get_drvdata(dev); + int error; + + error = mxt_load_fw(dev, data->fw_file); + if (error) { + dev_err(dev, "The firmware update failed(%d)\n", error); + count = error; + } else { + dev_dbg(dev, "The firmware update succeeded\n"); + } + return count; +} + +static ssize_t mxt_suspend_acq_interval_ms_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct mxt_data *data = dev_get_drvdata(dev); + u8 interval_reg = data->suspend_acq_interval; + u8 interval_ms = (interval_reg == 255) ? 0 : interval_reg; + return scnprintf(buf, PAGE_SIZE, "%u\n", interval_ms); +} + +static ssize_t mxt_suspend_acq_interval_ms_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mxt_data *data = dev_get_drvdata(dev); + int ret; + u32 param; + + ret = kstrtou32(buf, 10, ¶m); + if (ret < 0) + return -EINVAL; + + /* 0 ms inteval means "free run" */ + if (param == 0) + param = 255; + /* 254 ms is the largest interval */ + else if (param > 254) + param = 254; + + data->suspend_acq_interval = param; + return count; +} + +static DEVICE_ATTR(backupnv, S_IWUSR, NULL, mxt_backupnv_store); +static DEVICE_ATTR(calibrate, S_IWUSR, NULL, mxt_calibrate_store); +static DEVICE_ATTR(config_csum, S_IRUGO, mxt_config_csum_show, NULL); +static DEVICE_ATTR(fw_file, S_IRUGO | S_IWUSR, mxt_fw_file_show, + mxt_fw_file_store); +static DEVICE_ATTR(config_file, S_IRUGO | S_IWUSR, mxt_config_file_show, + mxt_config_file_store); +static DEVICE_ATTR(fw_version, S_IRUGO, mxt_fw_version_show, NULL); +static DEVICE_ATTR(hw_version, S_IRUGO, mxt_hw_version_show, NULL); +static DEVICE_ATTR(info_csum, S_IRUGO, mxt_info_csum_show, NULL); +static DEVICE_ATTR(matrix_size, S_IRUGO, mxt_matrix_size_show, NULL); +static DEVICE_ATTR(object, S_IWUSR, NULL, mxt_object_store); +static DEVICE_ATTR(update_config, S_IWUSR, NULL, mxt_update_config_store); +static DEVICE_ATTR(update_fw, S_IWUSR, NULL, mxt_update_fw_store); +static DEVICE_ATTR(suspend_acq_interval_ms, S_IRUGO | S_IWUSR, + mxt_suspend_acq_interval_ms_show, + mxt_suspend_acq_interval_ms_store); + +static struct attribute *mxt_attrs[] = { + &dev_attr_backupnv.attr, + &dev_attr_calibrate.attr, + &dev_attr_config_csum.attr, + &dev_attr_fw_file.attr, + &dev_attr_config_file.attr, + &dev_attr_fw_version.attr, + &dev_attr_hw_version.attr, + &dev_attr_info_csum.attr, + &dev_attr_matrix_size.attr, + &dev_attr_object.attr, + &dev_attr_update_config.attr, + &dev_attr_update_fw.attr, + NULL +}; + +static const struct attribute_group mxt_attr_group = { + .attrs = mxt_attrs, +}; + +static struct attribute *mxt_power_attrs[] = { + &dev_attr_suspend_acq_interval_ms.attr, + NULL +}; + +static const struct attribute_group mxt_power_attr_group = { + .name = power_group_name, + .attrs = mxt_power_attrs, +}; + +/* + ************************************************************** + * debugfs helper functions + ************************************************************** +*/ + +/* + * Print the formatted string into the end of string |*str| which has size + * |*str_size|. Extra space will be allocated to hold the formatted string + * and |*str_size| will be updated accordingly. + */ +static int mxt_asprintf(char **str, size_t *str_size, const char *fmt, ...) +{ + unsigned int len; + va_list ap, aq; + int ret; + char *str_tmp; + + va_start(ap, fmt); + va_copy(aq, ap); + len = vsnprintf(NULL, 0, fmt, aq); + va_end(aq); + + str_tmp = krealloc(*str, *str_size + len + 1, GFP_KERNEL); + if (str_tmp == NULL) + return -ENOMEM; + + *str = str_tmp; + + ret = vsnprintf(*str + *str_size, len + 1, fmt, ap); + va_end(ap); + + if (ret != len) + return -EINVAL; + + *str_size += len; + + return 0; +} + +static int mxt_object_fetch(struct mxt_data *data) +{ + size_t count = 0; + size_t i, j, k; + int ret = 0; + char *str = NULL; + u8 *obuf = NULL; + u8 *obuf_tmp = NULL; + + if (data->object_str) + return -EINVAL; + + for (i = 0; i < data->info.object_num; i++) { + struct mxt_object *object = &data->object_table[i]; + + if (!mxt_object_readable(object->type)) + continue; + + ret = mxt_asprintf(&str, &count, "\nType: %u\n", + object->type); + if (ret) + goto err; + + obuf_tmp = krealloc(obuf, object->size, GFP_KERNEL); + if (!obuf_tmp) { + ret = -ENOMEM; + goto err; + } + + obuf = obuf_tmp; + + for (j = 0; j < object->instances; j++) { + if (object->instances > 1) { + ret = mxt_asprintf(&str, &count, + "Instance: %zu\n", j); + if (ret) + goto err; + } + + ret = mxt_read_object(data, object, j, obuf); + if (ret) + goto err; + + for (k = 0; k < object->size; k++) { + ret = mxt_asprintf(&str, &count, + "\t[%2zu]: %02x (%d)\n", + k, obuf[k], obuf[k]); + if (ret) + goto err; + } + } + } + + goto done; + +err: + kfree(str); + str = NULL; + count = 0; +done: + data->object_str = str; + data->object_str_size = count; + kfree(obuf); + return ret; +} + +/* + ************************************************************** + * debugfs interface + ************************************************************** +*/ +static int mxt_debugfs_T37_open(struct inode *inode, struct file *file) +{ + struct mxt_data *mxt = inode->i_private; + int ret; + u8 cmd; + + if (file->f_dentry == mxt->dentry_deltas) + cmd = MXT_T6_CMD_DELTAS; + else if (file->f_dentry == mxt->dentry_refs) + cmd = MXT_T6_CMD_REFS; + else + return -EINVAL; + + /* Only allow one T37 debugfs file to be opened at a time */ + ret = mutex_lock_interruptible(&mxt->T37_buf_mutex); + if (ret) + return ret; + + if (!i2c_use_client(mxt->client)) { + ret = -ENODEV; + goto err_unlock; + } + + /* Fetch all T37 pages into mxt->T37_buf */ + ret = mxt_T37_fetch(mxt, cmd); + if (ret) + goto err_release; + + file->private_data = mxt; + + return 0; + +err_release: + i2c_release_client(mxt->client); +err_unlock: + mutex_unlock(&mxt->T37_buf_mutex); + return ret; +} + +static int mxt_debugfs_T37_release(struct inode *inode, struct file *file) +{ + struct mxt_data *mxt = file->private_data; + + file->private_data = NULL; + + kfree(mxt->T37_buf); + mxt->T37_buf = NULL; + mxt->T37_buf_size = 0; + + i2c_release_client(mxt->client); + mutex_unlock(&mxt->T37_buf_mutex); + + return 0; +} + + +/* Return some bytes from the buffered T37 object, starting from *ppos */ +static ssize_t mxt_debugfs_T37_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct mxt_data *mxt = file->private_data; + + if (!mxt->T37_buf) + return -ENODEV; + + if (*ppos >= mxt->T37_buf_size) + return 0; + + if (count + *ppos > mxt->T37_buf_size) + count = mxt->T37_buf_size - *ppos; + + if (copy_to_user(buffer, &mxt->T37_buf[*ppos], count)) + return -EFAULT; + + *ppos += count; + + return count; +} + +static const struct file_operations mxt_debugfs_T37_fops = { + .owner = THIS_MODULE, + .open = mxt_debugfs_T37_open, + .release = mxt_debugfs_T37_release, + .read = mxt_debugfs_T37_read +}; + +static int mxt_debugfs_object_open(struct inode *inode, struct file *file) +{ + struct mxt_data *mxt = inode->i_private; + int ret; + + /* Only allow one object debugfs file to be opened at a time */ + ret = mutex_lock_interruptible(&mxt->object_str_mutex); + if (ret) + return ret; + + if (!i2c_use_client(mxt->client)) { + ret = -ENODEV; + goto err_object_unlock; + } + + ret = mxt_object_fetch(mxt); + if (ret) + goto err_object_i2c_release; + file->private_data = mxt; + + return 0; + +err_object_i2c_release: + i2c_release_client(mxt->client); +err_object_unlock: + mutex_unlock(&mxt->object_str_mutex); + return ret; +} + +static int mxt_debugfs_object_release(struct inode *inode, struct file *file) +{ + struct mxt_data *mxt = file->private_data; + file->private_data = NULL; + + kfree(mxt->object_str); + mxt->object_str = NULL; + mxt->object_str_size = 0; + + i2c_release_client(mxt->client); + mutex_unlock(&mxt->object_str_mutex); + + return 0; +} + +static ssize_t mxt_debugfs_object_read(struct file *file, char __user* buffer, + size_t count, loff_t *ppos) +{ + struct mxt_data *mxt = file->private_data; + if (!mxt->object_str) + return -ENODEV; + + if (*ppos >= mxt->object_str_size) + return 0; + + if (count + *ppos > mxt->object_str_size) + count = mxt->object_str_size - *ppos; + + if (copy_to_user(buffer, &mxt->object_str[*ppos], count)) + return -EFAULT; + + *ppos += count; + + return count; +} + +static const struct file_operations mxt_debugfs_object_fops = { + .owner = THIS_MODULE, + .open = mxt_debugfs_object_open, + .release = mxt_debugfs_object_release, + .read = mxt_debugfs_object_read, +}; + +static int mxt_debugfs_init(struct mxt_data *mxt) +{ + struct device *dev = &mxt->client->dev; + + if (!mxt_debugfs_root) + return -ENODEV; + + mxt->dentry_dev = debugfs_create_dir(kobject_name(&dev->kobj), + mxt_debugfs_root); + + if (!mxt->dentry_dev) + return -ENODEV; + + mutex_init(&mxt->T37_buf_mutex); + + mxt->dentry_deltas = debugfs_create_file("deltas", S_IRUSR, + mxt->dentry_dev, mxt, + &mxt_debugfs_T37_fops); + mxt->dentry_refs = debugfs_create_file("refs", S_IRUSR, + mxt->dentry_dev, mxt, + &mxt_debugfs_T37_fops); + mutex_init(&mxt->object_str_mutex); + + mxt->dentry_object = debugfs_create_file("object", S_IRUGO, + mxt->dentry_dev, mxt, + &mxt_debugfs_object_fops); + return 0; +} + +static void mxt_debugfs_remove(struct mxt_data *mxt) +{ + if (mxt->dentry_dev) { + debugfs_remove_recursive(mxt->dentry_dev); + mutex_destroy(&mxt->object_str_mutex); + kfree(mxt->object_str); + mutex_destroy(&mxt->T37_buf_mutex); + kfree(mxt->T37_buf); + } +} + +static int mxt_save_regs(struct mxt_data *data, u8 type, u8 instance, + u8 offset, u8 *val, u16 size) +{ + struct mxt_object *object; + u16 addr; + int ret; + + object = mxt_get_object(data, type); + if (!object) + return -EINVAL; + + addr = object->start_address + instance * object->size + offset; + ret = mxt_read_reg(data->client, addr, size, val); + if (ret) + return -EINVAL; + + return 0; +} + +static int mxt_set_regs(struct mxt_data *data, u8 type, u8 instance, + u8 offset, const u8 *val, u16 size) +{ + struct mxt_object *object; + u16 addr; + int ret; + + object = mxt_get_object(data, type); + if (!object) + return -EINVAL; + + addr = object->start_address + instance * object->size + offset; + ret = mxt_write_reg(data->client, addr, size, val); + if (ret) + return -EINVAL; + + return 0; +} + +static void mxt_start(struct mxt_data *data) +{ + /* Enable touch reporting */ + mxt_write_object(data, MXT_TOUCH_MULTI_T9, 0, MXT_TOUCH_CTRL, + MXT_TOUCH_CTRL_OPERATIONAL); +} + +static void mxt_stop(struct mxt_data *data) +{ + /* Disable touch reporting */ + mxt_write_object(data, MXT_TOUCH_MULTI_T9, 0, MXT_TOUCH_CTRL, + MXT_TOUCH_CTRL_SCANNING); +} + +static int mxt_input_open(struct input_dev *dev) +{ + struct mxt_data *data = input_get_drvdata(dev); + + mxt_start(data); + + return 0; +} + +static void mxt_input_close(struct input_dev *dev) +{ + struct mxt_data *data = input_get_drvdata(dev); + + mxt_stop(data); +} + +static int mxt_input_dev_create(struct mxt_data *data) +{ + struct input_dev *input_dev; + int error; + int max_area_channels; + int max_touch_major; + + /* Don't need to register input_dev in bl mode */ + if (mxt_in_bootloader(data)) + return 0; + + error = mxt_calc_resolution(data); + if (error) + return error; + + /* Clear the existing one if it exists */ + if (data->input_dev) { + input_unregister_device(data->input_dev); + data->input_dev = NULL; + } + + data->input_dev = input_dev = input_allocate_device(); + if (!input_dev) + return -ENOMEM; + + input_dev->name = (data->is_tp) ? "Atmel maXTouch Touchpad" : + "Atmel maXTouch Touchscreen"; + input_dev->phys = data->client->adapter->name; + input_dev->id.bustype = BUS_I2C; + input_dev->dev.parent = &data->client->dev; + input_dev->open = mxt_input_open; + input_dev->close = mxt_input_close; + + __set_bit(EV_ABS, input_dev->evbit); + __set_bit(EV_KEY, input_dev->evbit); + __set_bit(BTN_TOUCH, input_dev->keybit); + + if (data->is_tp) { + __set_bit(INPUT_PROP_POINTER, input_dev->propbit); + __set_bit(INPUT_PROP_BUTTONPAD, input_dev->propbit); + + __set_bit(BTN_LEFT, input_dev->keybit); + __set_bit(BTN_TOOL_FINGER, input_dev->keybit); + __set_bit(BTN_TOOL_DOUBLETAP, input_dev->keybit); + __set_bit(BTN_TOOL_TRIPLETAP, input_dev->keybit); + __set_bit(BTN_TOOL_QUADTAP, input_dev->keybit); + __set_bit(BTN_TOOL_QUINTTAP, input_dev->keybit); + } + + /* For single touch */ + input_set_abs_params(input_dev, ABS_X, + 0, data->max_x, 0, 0); + input_set_abs_params(input_dev, ABS_Y, + 0, data->max_y, 0, 0); + input_set_abs_params(input_dev, ABS_PRESSURE, + 0, 255, 0, 0); + input_abs_set_res(input_dev, ABS_X, MXT_PIXELS_PER_MM); + input_abs_set_res(input_dev, ABS_Y, MXT_PIXELS_PER_MM); + + /* For multi touch */ + error = input_mt_init_slots(input_dev, MXT_MAX_FINGER); + if (error) + goto err_free_device; + + max_area_channels = min(255U, data->max_area_channels); + max_touch_major = get_touch_major_pixels(data, max_area_channels); + input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, + 0, max_touch_major, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_X, + 0, data->max_x, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_Y, + 0, data->max_y, 0, 0); + input_set_abs_params(input_dev, ABS_MT_PRESSURE, + 0, 255, 0, 0); + input_abs_set_res(input_dev, ABS_MT_POSITION_X, MXT_PIXELS_PER_MM); + input_abs_set_res(input_dev, ABS_MT_POSITION_Y, MXT_PIXELS_PER_MM); + + input_set_drvdata(input_dev, data); + + error = input_register_device(input_dev); + if (error) + goto err_free_device; + + return 0; + +err_free_device: + input_free_device(data->input_dev); + data->input_dev = NULL; + return error; +} + +static int mxt_initialize(struct mxt_data *data) +{ + struct i2c_client *client = data->client; + struct device *dev = &client->dev; + struct mxt_info *info = &data->info; + int error; + + /* Read 7-byte info block starting at address 0 */ + error = mxt_read_reg(client, MXT_INFO, sizeof(*info), info); + if (error) + return error; + + /* Get object table information */ + error = mxt_get_object_table(data); + if (error) + return error; + + /* Apply config from platform data */ + error = mxt_handle_pdata(data); + if (error) + return error; + + /* Soft reset */ + error = mxt_write_object(data, MXT_GEN_COMMAND_T6, 0, + MXT_COMMAND_RESET, 1); + if (error) + return error; + msleep(MXT_RESET_TIME); + + dev_info(dev, "Family ID: %d Variant ID: %d Major.Minor.Build: %d.%d.%d\n", + info->family_id, info->variant_id, info->version >> 4, + info->version & 0xf, info->build); + + dev_info(dev, "Matrix X Size: %d Matrix Y Size: %d Object Num: %d\n", + info->matrix_xsize, info->matrix_ysize, info->object_num); + + return 0; +} + + +static void mxt_initialize_async(void *closure, async_cookie_t cookie) +{ + struct mxt_data *data = closure; + struct i2c_client *client = data->client; + unsigned long irqflags; + int error; + + if (!mxt_in_bootloader(data)) { + error = mxt_initialize(data); + if (error) + goto error_free_object; + } else { + dev_info(&client->dev, "device came up in bootloader mode.\n"); + } + + error = mxt_input_dev_create(data); + if (error) + goto error_free_object; + + /* Default to falling edge if no platform data provided */ + irqflags = data->pdata ? data->pdata->irqflags : IRQF_TRIGGER_FALLING; + error = request_threaded_irq(client->irq, + NULL, + mxt_interrupt, + irqflags, + client->name, + data); + if (error) { + dev_err(&client->dev, "Failed to register interrupt\n"); + goto error_unregister_device; + } + + if (!mxt_in_bootloader(data)) { + error = mxt_handle_messages(data, true); + if (error) + goto error_free_irq; + } + + /* Force the device to report back status so we can cache the device + * config checksum + */ + error = mxt_write_object(data, MXT_GEN_COMMAND_T6, 0, + MXT_COMMAND_REPORTALL, 1); + if (error) + dev_warn(&client->dev, "error making device report status.\n"); + + error = sysfs_create_group(&client->dev.kobj, &mxt_attr_group); + if (error) + dev_warn(&client->dev, "error creating sysfs entries.\n"); + + error = sysfs_merge_group(&client->dev.kobj, &mxt_power_attr_group); + if (error) + dev_warn(&client->dev, "error merging power sysfs entries.\n"); + + error = mxt_debugfs_init(data); + if (error) + dev_warn(&client->dev, "error creating debugfs entries.\n"); + + return; + +error_free_irq: + free_irq(client->irq, data); +error_unregister_device: + input_unregister_device(data->input_dev); +error_free_object: + kfree(data->object_table); + kfree(data->fw_file); + kfree(data->config_file); + kfree(data); +} + +static int __devinit mxt_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct mxt_platform_data *pdata = client->dev.platform_data; + struct mxt_data *data; + int error; + + data = kzalloc(sizeof(struct mxt_data), GFP_KERNEL); + if (!data) { + dev_err(&client->dev, "Failed to allocate memory\n"); + return -ENOMEM; + } + + data->is_tp = !strcmp(id->name, "atmel_mxt_tp"); + + data->client = client; + i2c_set_clientdata(client, data); + + data->pdata = pdata; + data->irq = client->irq; + + data->suspend_acq_interval = MXT_SUSPEND_ACQINT_VALUE; + + error = mxt_update_file_name(&client->dev, &data->fw_file, MXT_FW_NAME, + strlen(MXT_FW_NAME)); + if (error) + goto err_free_object; + + error = mxt_update_file_name(&client->dev, &data->config_file, + MXT_CONFIG_NAME, strlen(MXT_CONFIG_NAME)); + if (error) + goto err_free_object; + + init_completion(&data->bl_completion); + init_completion(&data->auto_cal_completion); + + async_schedule(mxt_initialize_async, data); + + return 0; + +err_free_object: + kfree(data->object_table); + kfree(data->fw_file); + kfree(data->config_file); + kfree(data); + return error; +} + +static int __devexit mxt_remove(struct i2c_client *client) +{ + struct mxt_data *data = i2c_get_clientdata(client); + + mxt_debugfs_remove(data); + sysfs_unmerge_group(&client->dev.kobj, &mxt_power_attr_group); + sysfs_remove_group(&client->dev.kobj, &mxt_attr_group); + free_irq(data->irq, data); + input_unregister_device(data->input_dev); + kfree(data->object_table); + kfree(data->fw_file); + kfree(data->config_file); + kfree(data); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP + +static void mxt_suspend_enable_T9(struct mxt_data *data) +{ + struct device *dev = &data->client->dev; + u8 T9_ctrl = MXT_TOUCH_CTRL_ENABLE | MXT_TOUCH_CTRL_RPTEN; + int ret; + unsigned long timeout = msecs_to_jiffies(350); + bool need_enable = false; + bool need_report = false; + + dev_dbg(dev, "Current T9_Ctrl is %x\n", data->T9_ctrl); + + need_enable = !(data->T9_ctrl & MXT_TOUCH_CTRL_ENABLE); + need_report = !(data->T9_ctrl & MXT_TOUCH_CTRL_RPTEN); + + /* If already enabled and reporting, do nothing */ + if (!need_enable && !need_report) + return; + + /* If the ENABLE bit is toggled, there will be auto-calibration msg. + * We will have to clear this msg before going into suspend otherwise + * it will wake up the device immediately + */ + if (need_enable) + INIT_COMPLETION(data->auto_cal_completion); + + /* Enable T9 object (ENABLE and REPORT) */ + ret = mxt_set_regs(data, MXT_TOUCH_MULTI_T9, 0, 0, + &T9_ctrl, 1); + if (ret) { + dev_err(dev, "Set T9 ctrl config failed, %d\n", ret); + return; + } + + if (need_enable) { + ret = wait_for_completion_interruptible_timeout( + &data->auto_cal_completion, timeout); + if (ret <= 0) + dev_err(dev, "Wait for auto cal completion failed.\n"); + } +} + +static int mxt_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct mxt_data *data = i2c_get_clientdata(client); + struct input_dev *input_dev = data->input_dev; + u8 T7_config_idle[3] = {data->suspend_acq_interval, + data->suspend_acq_interval, + 0}; + u8 T7_config_deepsleep[3] = {0x00, 0x00, 0}; + u8 *power_config; + int ret; + + mutex_lock(&input_dev->mutex); + + /* Save 3 bytes T7 Power config */ + ret = mxt_save_regs(data, MXT_GEN_POWER_T7, 0, 0, + data->T7_config, 3); + if (ret) + dev_err(dev, "Save T7 Power config failed, %d\n", ret); + data->T7_config_valid = (ret == 0); + + /* Set T7 to idle mode if we allow wakeup from touch, otherwise + * put it into deepsleep mode. + */ + power_config = device_may_wakeup(dev) ? T7_config_idle + : T7_config_deepsleep; + + ret = mxt_set_regs(data, MXT_GEN_POWER_T7, 0, 0, + power_config, 3); + if (ret) + dev_err(dev, "Set T7 Power config failed, %d\n", ret); + + /* Save 1 byte T9 Ctrl config */ + ret = mxt_save_regs(data, MXT_TOUCH_MULTI_T9, 0, 0, + &data->T9_ctrl, 1); + if (ret) + dev_err(dev, "Save T9 ctrl config failed, %d\n", ret); + data->T9_ctrl_valid = (ret == 0); + + if (device_may_wakeup(dev)) { + /* If we allow wakeup from touch, we have to enable T9 so + * that IRQ can be generated from touch + */ + + /* Set proper T9 ENABLE & REPTN bits */ + if (data->T9_ctrl_valid) + mxt_suspend_enable_T9(data); + + /* Enable wake from IRQ */ + data->irq_wake = (enable_irq_wake(data->irq) == 0); + } else if (input_dev->users) { + mxt_stop(data); + } + + disable_irq(data->irq); + + mutex_unlock(&input_dev->mutex); + + return 0; +} + +static int mxt_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct mxt_data *data = i2c_get_clientdata(client); + struct input_dev *input_dev = data->input_dev; + int ret; + + /* Process any pending message so that CHG line can be de-asserted */ + ret = mxt_handle_messages(data, false); + if (ret) + dev_err(dev, "Handling message fails upon resume, %d\n", ret); + + mxt_release_all_fingers(data); + + mutex_lock(&input_dev->mutex); + + /* Restore the T9 Ctrl config to before-suspend value */ + if (data->T9_ctrl_valid) { + ret = mxt_set_regs(data, MXT_TOUCH_MULTI_T9, 0, 0, + &data->T9_ctrl, 1); + if (ret) + dev_err(dev, "Set T9 ctrl config failed, %d\n", ret); + } + + /* Restore the T7 Power config to before-suspend value */ + if (data->T7_config_valid) { + ret = mxt_set_regs(data, MXT_GEN_POWER_T7, 0, 0, + data->T7_config, 3); + if (ret) + dev_err(dev, "Set T7 power config failed, %d\n", ret); + } + + if (!device_may_wakeup(dev)) { + /* Recalibration in case of environment change */ + ret = mxt_write_object(data, MXT_GEN_COMMAND_T6, 0, + MXT_COMMAND_CALIBRATE, 1); + if (ret) + dev_err(dev, "Resume recalibration failed %d\n", ret); + msleep(MXT_CAL_TIME); + } + + mutex_unlock(&input_dev->mutex); + + enable_irq(data->irq); + + if (device_may_wakeup(dev) && data->irq_wake) + disable_irq_wake(data->irq); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(mxt_pm_ops, mxt_suspend, mxt_resume); + +static const struct i2c_device_id mxt_id[] = { + { "qt602240_ts", 0 }, + { "atmel_mxt_ts", 0 }, + { "atmel_mxt_tp", 0 }, + { "mXT224", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mxt_id); + +static struct i2c_driver mxt_driver = { + .driver = { + .name = "atmel_mxt_ts", + .owner = THIS_MODULE, + .pm = &mxt_pm_ops, + }, + .probe = mxt_probe, + .remove = __devexit_p(mxt_remove), + .id_table = mxt_id, +}; + +static int __init mxt_init(void) +{ + /* Create a global debugfs root for all atmel_mxt_ts devices */ + mxt_debugfs_root = debugfs_create_dir(mxt_driver.driver.name, NULL); + if (mxt_debugfs_root == ERR_PTR(-ENODEV)) + mxt_debugfs_root = NULL; + + return i2c_add_driver(&mxt_driver); +} + +static void __exit mxt_exit(void) +{ + if (mxt_debugfs_root) + debugfs_remove_recursive(mxt_debugfs_root); + + i2c_del_driver(&mxt_driver); +} + +module_init(mxt_init); +module_exit(mxt_exit); + +/* Module information */ +MODULE_AUTHOR("Joonyoung Shim <jy0922.shim@samsung.com>"); +MODULE_DESCRIPTION("Atmel maXTouch Touchscreen driver"); +MODULE_LICENSE("GPL"); diff --git a/chromeos_laptop.c b/chromeos_laptop.c new file mode 100644 index 0000000..f948f61 --- /dev/null +++ b/chromeos_laptop.c @@ -0,0 +1,478 @@ +/* + * chromeos_laptop.c - Driver to instantiate Chromebook i2c/smbus and platform + * devices. + * + * Copyright (C) 2012 Google, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/dmi.h> +#include <linux/i2c.h> +#include <linux/i2c/atmel_mxt_ts.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#define ATMEL_TP_I2C_ADDR 0x4b +#define ATMEL_TP_I2C_BL_ADDR 0x25 +#define ATMEL_TP2_I2C_ADDR 0x4a +#define ATMEL_TP2_I2C_BL_ADDR 0x26 +#define CYAPA_TP_I2C_ADDR 0x67 +#define ISL_ALS_I2C_ADDR 0x44 +#define TAOS_ALS_I2C_ADDR 0x29 + +static struct i2c_client *als; +static struct i2c_client *tp; +static struct i2c_client *tp2; + +const char *i2c_adapter_names[] = { + "SMBus I801 adapter", + "i915 gmbus vga", + "i915 gmbus panel", +}; + +/* Keep this enum consistent with i2c_adapter_names */ +enum i2c_adapter_type { + I2C_ADAPTER_SMBUS = 0, + I2C_ADAPTER_VGADDC, + I2C_ADAPTER_PANEL, +}; + +static struct i2c_board_info __initdata cyapa_device = { + I2C_BOARD_INFO("cyapa", CYAPA_TP_I2C_ADDR), + .flags = I2C_CLIENT_WAKE, +}; + +static struct i2c_board_info __initdata isl_als_device = { + I2C_BOARD_INFO("isl29018", ISL_ALS_I2C_ADDR), +}; + +static struct i2c_board_info __initdata tsl2583_als_device = { + I2C_BOARD_INFO("tsl2583", TAOS_ALS_I2C_ADDR), +}; + +static struct i2c_board_info __initdata tsl2563_als_device = { + I2C_BOARD_INFO("tsl2563", TAOS_ALS_I2C_ADDR), +}; + +static const u8 atmel_224e_tp_config_data[] = { + /* MXT_GEN_COMMAND(6) */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* MXT_GEN_POWERCONFIG(7) */ + 0xff, 0xff, 0x32, + /* MXT_GEN_ACQUIRE(8) */ + 0x06, 0x00, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* MXT_TOUCH_MULTI(9) */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x02, 0x01, 0x00, 0x0a, 0x03, 0x03, 0x0a, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x37, 0x37, 0x00, + /* MXT_TOUCH_KEYARRAY(15) */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, + /* MXT_SPT_COMMSCONFIG(18) */ + 0x00, 0x00, + /* MXT_SPT_GPIOPWM(19) */ + 0x03, 0xDF, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* MXT_TOUCH_PROXIMITY(23) */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + /* MXT_SPT_SELFTEST(25) */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + /* MXT_PROCI_GRIPSUPPRESSION(40) */ + 0x00, 0x00, 0x00, 0x00, 0x00, + /* MXT_PROCI_TOUCHSUPPRESSION(42) */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* MXT_SPT_CTECONFIG(46) */ + 0x00, 0x02, 0x0a, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, + /* MXT_PROCI_STYLUS(47) */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* MXT_PROCG_NOISESUPPRESSION(48) */ + 0x01, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; + +static struct mxt_platform_data atmel_224e_tp_platform_data = { + .x_line = 18, + .y_line = 12, + .x_size = 102*20, + .y_size = 68*20, + .blen = 0x20, /* Gain setting is in upper 4 bits */ + .threshold = 0x19, + .voltage = 0, /* 3.3V */ + .orient = MXT_HORIZONTAL_FLIP, + .irqflags = IRQF_TRIGGER_FALLING, + .config = atmel_224e_tp_config_data, + .config_length = sizeof(atmel_224e_tp_config_data), +}; + +static struct i2c_board_info __initdata atmel_224e_tp_device = { + I2C_BOARD_INFO("atmel_mxt_tp", ATMEL_TP_I2C_ADDR), + .platform_data = &atmel_224e_tp_platform_data, + .flags = I2C_CLIENT_WAKE, +}; + +static struct i2c_board_info __initdata atmel_224s_tp_device = { + I2C_BOARD_INFO("atmel_mxt_tp", ATMEL_TP_I2C_ADDR), + .platform_data = NULL, + .flags = I2C_CLIENT_WAKE, +}; + +static struct i2c_board_info __initdata atmel_tp2_device = { + I2C_BOARD_INFO("atmel_mxt_ts", ATMEL_TP2_I2C_ADDR), + .platform_data = NULL, + .irq = 22, + .flags = I2C_CLIENT_WAKE, +}; + +static __init struct i2c_client *__add_probed_i2c_device( + const char *name, + int bus, + struct i2c_board_info *info, + const unsigned short *addrs) +{ + const struct dmi_device *dmi_dev; + const struct dmi_dev_onboard *dev_data; + struct i2c_adapter *adapter; + struct i2c_client *client; + + if (bus < 0) + return NULL; + /* + * If a name is specified, look for irq platform information stashed + * in DMI_DEV_TYPE_DEV_ONBOARD. + */ + if (name) { + dmi_dev = dmi_find_device(DMI_DEV_TYPE_DEV_ONBOARD, name, NULL); + if (!dmi_dev) { + pr_err("%s failed to dmi find device %s.\n", + __func__, + name); + return NULL; + } + dev_data = (struct dmi_dev_onboard *)dmi_dev->device_data; + if (!dev_data) { + pr_err("%s failed to get data from dmi for %s.\n", + __func__, name); + return NULL; + } + info->irq = dev_data->instance; + } + + adapter = i2c_get_adapter(bus); + if (!adapter) { + pr_err("%s failed to get i2c adapter %d.\n", __func__, bus); + return NULL; + } + + /* add the i2c device */ + client = i2c_new_probed_device(adapter, info, addrs, NULL); + if (!client) + pr_err("%s failed to register device %d-%02x\n", + __func__, bus, info->addr); + else + pr_debug("%s added i2c device %d-%02x\n", + __func__, bus, info->addr); + + i2c_put_adapter(adapter); + return client; +} + +static int __init __find_i2c_adap(struct device *dev, void *data) +{ + const char *name = data; + const char *prefix = "i2c-"; + struct i2c_adapter *adapter; + if (strncmp(dev_name(dev), prefix, strlen(prefix))) + return 0; + adapter = to_i2c_adapter(dev); + return !strncmp(adapter->name, name, strlen(name)); +} + +static int __init find_i2c_adapter_num(enum i2c_adapter_type type) +{ + struct device *dev = NULL; + struct i2c_adapter *adapter; + const char *name = i2c_adapter_names[type]; + /* find the adapter by name */ + dev = bus_find_device(&i2c_bus_type, NULL, (void *)name, + __find_i2c_adap); + if (!dev) { + pr_err("%s: i2c adapter %s not found on system.\n", __func__, + name); + return -ENODEV; + } + adapter = to_i2c_adapter(dev); + return adapter->nr; +} + +/* + * Takes a list of addresses in addrs as such : + * { addr1, ... , addrn, I2C_CLIENT_END }; + * chromeos_laptop_add_probed_i2c_device will use i2c_new_probed_device + * and probe for devices at all of the addresses listed. + * Returns NULL if no devices found. + * See Documentation/i2c/instantiating-devices for more information. + */ +static __init struct i2c_client *chromeos_laptop_add_probed_i2c_device( + const char *name, + enum i2c_adapter_type type, + struct i2c_board_info *info, + const unsigned short *addrs) +{ + return __add_probed_i2c_device(name, + find_i2c_adapter_num(type), + info, + addrs); +} + +/* + * Probes for a device at a single address, the one provided by + * info->addr. + * Returns NULL if no device found. + */ +static __init struct i2c_client *chromeos_laptop_add_i2c_device( + const char *name, + enum i2c_adapter_type type, + struct i2c_board_info *info) +{ + const unsigned short addr_list[] = { info->addr, I2C_CLIENT_END }; + return __add_probed_i2c_device(name, + find_i2c_adapter_num(type), + info, + addr_list); +} + +static __init struct i2c_client *add_smbus_device(const char *name, + struct i2c_board_info *info) +{ + return chromeos_laptop_add_i2c_device(name, I2C_ADAPTER_SMBUS, info); +} + +static int __init setup_link_tp2(const struct dmi_system_id *id) +{ + const unsigned short addr_list[] = { ATMEL_TP2_I2C_BL_ADDR, + ATMEL_TP2_I2C_ADDR, + I2C_CLIENT_END }; + + tp2 = chromeos_laptop_add_probed_i2c_device(NULL, + I2C_ADAPTER_PANEL, + &atmel_tp2_device, + addr_list); + return 0; +} + +static int __init setup_cyapa_smbus_tp(const struct dmi_system_id *id) +{ + /* add cyapa touchpad */ + tp = add_smbus_device("trackpad", &cyapa_device); + return 0; +} + +static int __init setup_link_tp(const struct dmi_system_id *id) +{ + const unsigned short atmel_addr_list[] = { ATMEL_TP_I2C_BL_ADDR, + ATMEL_TP_I2C_ADDR, + I2C_CLIENT_END }; + + /* first try cyapa touchpad */ + tp = chromeos_laptop_add_i2c_device("trackpad", + I2C_ADAPTER_VGADDC, + &cyapa_device); + if (tp) + return 0; + + /* then try atmel mxt touchpad */ + tp = chromeos_laptop_add_probed_i2c_device("trackpad", + I2C_ADAPTER_VGADDC, + &atmel_224s_tp_device, + atmel_addr_list); + return 0; +} + +static int __init setup_lumpy_tp(const struct dmi_system_id *id) +{ + /* first try cyapa touchpad on smbus */ + setup_cyapa_smbus_tp(id); + if (tp) + return 0; + + /* then try atmel mxt touchpad */ + tp = chromeos_laptop_add_i2c_device("trackpad", + I2C_ADAPTER_VGADDC, + &atmel_224e_tp_device); + return 0; +} + +static int __init setup_isl29018_als(const struct dmi_system_id *id) +{ + /* add isl29018 light sensor */ + als = add_smbus_device("lightsensor", &isl_als_device); + return 0; +} + +static int __init setup_isl29023_als(const struct dmi_system_id *id) +{ + /* add isl29023 light sensor on Panel DDC GMBus */ + als = chromeos_laptop_add_i2c_device("lightsensor", + I2C_ADAPTER_PANEL, + &isl_als_device); + return 0; +} + +static int __init setup_tsl2583_als(const struct dmi_system_id *id) +{ + /* add tsl2583 light sensor */ + als = add_smbus_device(NULL, &tsl2583_als_device); + return 0; +} + +static int __init setup_tsl2563_als(const struct dmi_system_id *id) +{ + /* add tsl2563 light sensor */ + als = add_smbus_device(NULL, &tsl2563_als_device); + return 0; +} + +static struct platform_device *kb_backlight_device; + +static int __init setup_keyboard_backlight(const struct dmi_system_id *id) +{ + kb_backlight_device = + platform_device_register_simple("chromeos-keyboard-leds", + -1, NULL, 0); + if (IS_ERR(kb_backlight_device)) { + pr_warn("Error registering Chrome OS keyboard LEDs.\n"); + kb_backlight_device = NULL; + } + return 0; +} + +static const struct __initdata dmi_system_id chromeos_laptop_dmi_table[] = { + { + .ident = "Lumpy - Touchpads", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Lumpy"), + }, + .callback = setup_lumpy_tp, + }, + { + .ident = "Link - Touchpads2", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Link"), + }, + .callback = setup_link_tp2, + }, + { + .ident = "Link - Touchpads", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Link"), + }, + .callback = setup_link_tp, + }, + { + .ident = "isl29018 - Light Sensor", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Lumpy"), + }, + .callback = setup_isl29018_als, + }, + { + .ident = "isl29023 - Light Sensor", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Link"), + }, + .callback = setup_isl29023_als, + }, + { + .ident = "Parrot - Touchpad", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Parrot"), + }, + .callback = setup_cyapa_smbus_tp, + }, + { + .ident = "Butterfy - Touchpad", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Butterfly"), + }, + .callback = setup_cyapa_smbus_tp, + }, + { + .ident = "tsl2583 - Light Sensor", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Alex"), + }, + .callback = setup_tsl2583_als, + }, + { + .ident = "tsl2563 - Light Sensor", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Mario"), + }, + .callback = setup_tsl2563_als, + }, + { + .ident = "tsl2563 - Light Sensor", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "ZGB"), + }, + .callback = setup_tsl2563_als, + }, + { + .ident = "Link - Keyboard backlight", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Link"), + }, + .callback = setup_keyboard_backlight, + }, + { } +}; + +static int __init chromeos_laptop_init(void) +{ + if (!dmi_check_system(chromeos_laptop_dmi_table)) { + pr_debug("%s unsupported system.\n", __func__); + return -ENODEV; + } + return 0; +} + +static void __exit chromeos_laptop_exit(void) +{ + if (als) + i2c_unregister_device(als); + if (tp) + i2c_unregister_device(tp); + if (tp2) + i2c_unregister_device(tp2); + if (kb_backlight_device) + platform_device_unregister(kb_backlight_device); +} + +module_init(chromeos_laptop_init); +module_exit(chromeos_laptop_exit); + +MODULE_DESCRIPTION("Chrome OS Laptop driver"); +MODULE_AUTHOR("Benson Leung <bleung@chromium.org>"); +MODULE_LICENSE("GPL"); |