misc: add a6 driver
diff --git a/drivers/misc/a6/a6.c b/drivers/misc/a6/a6.c
new file mode 100644
index 0000000..81d5b54
--- /dev/null
+++ b/drivers/misc/a6/a6.c
@@ -0,0 +1,5193 @@
+/*
+ * Includes
+ */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/list.h>
+#include <linux/wait.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/wait.h>
+#include <linux/bitops.h>
+#include <linux/ctype.h>
+#include <linux/slab.h>
+#include <linux/a6_sbw_interface.h>
+#include <linux/a6.h>
+#ifdef CONFIG_HIGH_RES_TIMERS
+#include <linux/hrtimer.h>
+#endif
+#include <linux/cpufreq.h>
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+#include <linux/mutex.h>
+#include <linux/sysfs.h>
+#include <linux/uaccess.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/poll.h>
+#include <asm/io.h>
+#include <linux/gpio.h>
+#include <linux/cdev.h>
+#include <linux/debugfs.h>
+#include <linux/switch.h>
+#include <linux/power_supply.h>
+#include <mach/msm_hsusb.h>
+#include <linux/max8903b_charger.h>
+
+#include "high_level_funcs.h"
+
+void max8903b_set_connected_ps(unsigned connected);
+
+#define A2A_RD_BUFF_SIZE (4 * 1024)
+#define A2A_WR_BUFF_SIZE (4 * 1024)
+
+#define A6_DEBUG
+#define A6_PQ
+//#define A6_I2C_RETRY
+
+#ifdef A6_DEBUG
+#define ASSERT(i) BUG_ON(!(i))
+
+#else
+#define ASSERT(i) ((void)0)
+
+#endif
+
+
+enum {
+ A6_DEBUG_VERBOSE = 0x01,
+};
+
+static int a6_debug_mask = 0x0;
+static int a6_tp_irq_count = 0;
+static int a6_t2s_dup_correct = 0;
+static int a6_disable_dock_switch = 0;
+
+static int param_set_disable_dock_switch(const char *val, struct kernel_param *kp);
+param_check_int(disable_dock_switch, &(a6_disable_dock_switch));
+module_param_call(disable_dock_switch, param_set_disable_dock_switch,
+ param_get_int, &a6_disable_dock_switch,
+ S_IRUGO | S_IWUSR | S_IWGRP
+ );
+__MODULE_PARM_TYPE(disable_dock_switch, int);
+
+module_param_named(
+ debug_mask, a6_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP
+ );
+
+module_param_named(
+ a6_irq_count, a6_tp_irq_count, int, S_IRUGO | S_IWUSR | S_IWGRP
+ );
+
+module_param_named(
+ t2s_dup_correct, a6_t2s_dup_correct, int, S_IRUGO | S_IWUSR | S_IWGRP
+ );
+
+#define A6_DPRINTK(mask, level, message, ...) \
+ do { \
+ if ((mask) & a6_debug_mask) \
+ printk(level message , ##__VA_ARGS__); \
+} while (0)
+
+#define PROFILE_USAGE
+#if defined PROFILE_USAGE
+bool reset_active = false;
+uint32_t start_time;
+int32_t diff_time;
+
+uint32_t start_last_a6_activity = 0;
+#endif
+
+long a6_last_ps_connect = 0;
+
+/* page 0x00 */
+/* host interrupts */
+#define TS2_I2C_INT_MASK_0 0x0000
+#define TS2_I2C_INT_MASK_1 0x0001
+#define TS2_I2C_INT_MASK_2 0x0002
+#define TS2_I2C_INT_MASK_3 0x0003
+#define TS2_I2C_INT_STATUS_0 0x0004
+#define TS2_I2C_INT_STATUS_1 0x0005
+#define TS2_I2C_INT_STATUS_2 0x0006
+#define TS2_I2C_INT_STATUS_3 0x0007
+#define TS2_I2C_INT_0_GPIOA_7 0x80
+#define TS2_I2C_INT_0_GPIOA_6 0x40
+#define TS2_I2C_INT_0_GPIOA_5 0x20
+#define TS2_I2C_INT_0_GPIOA_4 0x10
+#define TS2_I2C_INT_0_GPIOA_3 0x08
+#define TS2_I2C_INT_0_GPIOA_2 0x04
+#define TS2_I2C_INT_0_GPIOA_1 0x02
+#define TS2_I2C_INT_0_GPIOA_0 0x01
+#define TS2_I2C_INT_0_GPIOA_MASK 0xff
+#define TS2_I2C_INT_1_COMM_RX_FULL 0x02
+#define TS2_I2C_INT_1_COMM_TX_EMPTY 0x01
+#define TS2_I2C_INT_2_BAT_TEMP_HIGH 0x20
+#define TS2_I2C_INT_2_BAT_TEMP_LOW 0x10
+#define TS2_I2C_INT_2_BAT_VOLT_LOW 0x08
+#define TS2_I2C_INT_2_BAT_RARC_CRIT 0x04
+#define TS2_I2C_INT_2_BAT_RARC_LOW2 0x02
+#define TS2_I2C_INT_2_BAT_RARC_LOW1 0x01
+#define TS2_I2C_INT_3_A2A_CONNECT_CHANGE 0x08
+#define TS2_I2C_INT_3_FLAGS_CHANGE 0x04
+#define TS2_I2C_INT_3_LOG 0x02
+#define TS2_I2C_INT_3_RESET 0x01
+
+
+/* page 0x01 */
+/* battery (airboard only); interface defined by phone teams */
+#define TS2_I2C_BAT_STATUS 0x0100
+#define TS2_I2C_BAT_RARC 0x0101
+#define TS2_I2C_BAT_RSRC 0x0102
+#define TS2_I2C_BAT_AVG_CUR_MSB 0x0103
+#define TS2_I2C_BAT_AVG_CUR_LSB 0x0104
+#define TS2_I2C_BAT_TEMP_MSB 0x0105
+#define TS2_I2C_BAT_TEMP_LSB 0x0106
+#define TS2_I2C_BAT_VOLT_MSB 0x0107
+#define TS2_I2C_BAT_VOLT_LSB 0x0108
+#define TS2_I2C_BAT_CUR_MSB 0x0109
+#define TS2_I2C_BAT_CUR_LSB 0x010a
+#define TS2_I2C_BAT_COULOMB_MSB 0x010b
+#define TS2_I2C_BAT_COULOMB_LSB 0x010c
+#define TS2_I2C_BAT_AS 0x010d
+#define TS2_I2C_BAT_FULL_MSB 0x010e
+#define TS2_I2C_BAT_FULL_LSB 0x010f
+#define TS2_I2C_BAT_FULL40_MSB 0x0110
+#define TS2_I2C_BAT_FULL40_LSB 0x0111
+#define TS2_I2C_BAT_RSNSP 0x0112
+#define TS2_I2C_BAT_RAAC_MSB 0x0113
+#define TS2_I2C_BAT_RAAC_LSB 0x0114
+#define TS2_I2C_BAT_SACR_MSB 0x0115
+#define TS2_I2C_BAT_SACR_LSB 0x0116
+#define TS2_I2C_BAT_ASL 0x0117
+#define TS2_I2C_BAT_FAC_MSB 0x0118
+#define TS2_I2C_BAT_FAC_LSB 0x0119
+
+#define TS2_I2C_BAT_ROMID_0 0x0120
+#define TS2_I2C_BAT_ROMID(x) \
+ (TS2_I2C_BAT_ROMID_0 + (x))
+
+#define TS2_I2C_BAT_COMMAND_STATUS 0x0140
+#define TS2_I2C_BAT_COMMAND_AUTH 0x81
+#define TS2_I2C_BAT_COMMAND_REFRESH 0x82
+#define TS2_I2C_BAT_COMMAND_WAKE 0x83
+#define TS2_I2C_BAT_COMMAND_OFF 0xe9
+#define TS2_I2C_BAT_STATUS_AUTH_FAIL 0x08
+#define TS2_I2C_BAT_STATUS_AUTH_PASS 0x04
+#define TS2_I2C_BAT_STATUS_REGS_VALID 0x02
+#define TS2_I2C_BAT_STATUS_BUSY 0x01
+
+
+/* battery configuration (airboard only) */
+#define TS2_I2C_BAT_TEMP_LOW_MSB 0x0180
+#define TS2_I2C_BAT_TEMP_LOW_LSB 0x0181
+#define TS2_I2C_BAT_TEMP_HIGH_MSB 0x0182
+#define TS2_I2C_BAT_TEMP_HIGH_LSB 0x0183
+#define TS2_I2C_BAT_VOLT_LOW_MSB 0x0184
+#define TS2_I2C_BAT_VOLT_LOW_LSB 0x0185
+#define TS2_I2C_BAT_RARC_CRIT 0x0186
+#define TS2_I2C_BAT_RARC_LOW_2 0x0187
+#define TS2_I2C_BAT_RARC_LOW_1 0x0188
+
+#define TS2_I2C_BAT_CHALLENGE_0 0x01e0
+#define TS2_I2C_BAT_CHALLENGE(x) \
+ (TS2_I2C_BAT_CHALLENGE_0 + (x))
+#define TS2_I2C_BAT_RESPONSE_0 \
+ (TS2_I2C_BAT_CHALLENGE_0 + 8)
+#define TS2_I2C_BAT_RESPONSE(x) \
+ (TS2_I2C_BAT_RESPONSE_0 + (x))
+
+
+/* page 0x02 */
+/* comms */
+#define TS2_I2C_COMM_STATUS 0x0200
+#define TS2_I2C_COMM_STATUS_RX_FULL 0x02
+#define TS2_I2C_COMM_STATUS_TX_EMPTY 0x01
+#define TS2_I2C_COMM_TXDATA_RXDATA_RESERVED_1 0x0201 /* reserved 1 */
+#define TS2_I2C_COMM_TXDATA_RXDATA_RESERVED_2 0x0202 /* reserved 2 */
+#define TS2_I2C_COMM_TXDATA_RXDATA 0x0203 /* side-effect */
+
+/* page 0x03 */
+/* log */
+#define TS2_I2C_LOG_LEVEL 0x0300
+#define TS2_I2C_LOG_INT_THRESHOLD 0x0301
+#define TS2_I2C_LOG_LOST_MSB_RESERVED_1 0x0302 /* reserved 1 */
+#define TS2_I2C_LOG_LOST_MSB_RESERVED_2 0x0303 /* reserved 2 */
+#define TS2_I2C_LOG_LOST_MSB 0x0304 /* side-effect */
+#define TS2_I2C_LOG_LOST_LSB 0x0305
+#define TS2_I2C_LOG_COUNT_MSB 0x0306
+#define TS2_I2C_LOG_COUNT_LSB 0x0307
+#define TS2_I2C_LOG_ENTRY_MSB_RESERVED_1 0x0308 /* reserved 1 */
+#define TS2_I2C_LOG_ENTRY_MSB_RESERVED_2 0x0309 /* reserved 2 */
+#define TS2_I2C_LOG_ENTRY_MSB 0x030a /* side-effect */
+#define TS2_I2C_LOG_ENTRY_LSB 0x030b
+
+
+/* page 0x04 */
+/* local enumeration structure */
+/* Enumeration registers (local) */
+#define TS2_I2C_ENUM_STRUCT_VER 0x0400
+#define TS2_I2C_ENUM_STRUCT_LEN 0x0401
+#define TS2_I2C_ENUM_PROTOCOL_MAP_VER 0x0402
+#define TS2_I2C_ENUM_MFGR_ID_HI 0x0403
+#define TS2_I2C_ENUM_MFGR_ID_LO 0x0404
+#define TS2_I2C_ENUM_PRODUCT_TYPE_HI 0x0405
+#define TS2_I2C_ENUM_PRODUCT_TYPE_LO 0x0406
+#define TS2_I2C_ENUM_SERNO_7 0x0407
+#define TS2_I2C_ENUM_SERNO_6 0x0408
+#define TS2_I2C_ENUM_SERNO_5 0x0409
+#define TS2_I2C_ENUM_SERNO_4 0x040A
+#define TS2_I2C_ENUM_SERNO_3 0x040B
+#define TS2_I2C_ENUM_SERNO_2 0x040C
+#define TS2_I2C_ENUM_SERNO_1 0x040D
+#define TS2_I2C_ENUM_SERNO_0 0x040E
+#define TS2_I2C_ENUM_ASSY_REV 0x040F
+#define TS2_I2C_ENUM_FW_VER_2 0x0410
+#define TS2_I2C_ENUM_FW_VER_1 0x0411
+#define TS2_I2C_ENUM_FW_VER_0 0x0412
+#define TS2_I2C_ENUM_VNODE_MIN_HI 0x0413
+#define TS2_I2C_ENUM_VNODE_MIN_LO 0x0414
+#define TS2_I2C_ENUM_VNODE_MAX_HI 0x0415
+#define TS2_I2C_ENUM_VNODE_MAX_LO 0x0416
+#define TS2_I2C_ENUM_INODE_MIN_HI 0x0417
+#define TS2_I2C_ENUM_INODE_MIN_LO 0x0418
+#define TS2_I2C_ENUM_INODE_MAX_HI 0x0419
+#define TS2_I2C_ENUM_INODE_MAX_LO 0x041A
+#define TS2_I2C_ENUM_TNODE_MIN 0x041B
+#define TS2_I2C_ENUM_TNODE_MAX 0x041C
+#define TS2_I2C_ENUM_POWER_MAX 0x041D
+
+#define TS2_I2C_ENUM_ACCE_0 0x0428
+#define TS2_I2C_ENUM_ACCE_1 0x0429
+#define TS2_I2C_ENUM_ACCE_2 0x042A
+#define TS2_I2C_ENUM_ACCE_3 0x042B
+#define TS2_I2C_ENUM_ACCE_4 0x042C
+#define TS2_I2C_ENUM_ACCE_5 0x042D
+#define TS2_I2C_ENUM_ACCE_6 0x042E
+#define TS2_I2C_ENUM_ACCE_7 0x042F
+#define TS2_I2C_ENUM_ACCE_8 0x0430
+#define TS2_I2C_ENUM_ACCE_9 0x0431
+#define TS2_I2C_ENUM_ACCE_10 0x0432
+#define TS2_I2C_ENUM_ACCE_11 0x0433
+#define TS2_I2C_ENUM_ACCE_12 0x0434
+#define TS2_I2C_ENUM_ACCE_13 0x0435
+#define TS2_I2C_ENUM_ACCE_14 0x0436
+#define TS2_I2C_ENUM_ACCE_15 0x0437
+#define TS2_I2C_ENUM_MIN_PWM 0x0438
+
+
+/* page 0x05 */
+/* remote enumeration structure */
+#define TS2_I2C_ENUM_REMOTE_STRUCT_VER 0x0500
+#define TS2_I2C_ENUM_REMOTE_STRUCT_LEN 0x0501
+#define TS2_I2C_ENUM_REMOTE_PROTOCOL_MAP_VER 0x0502
+#define TS2_I2C_ENUM_REMOTE_MFGR_ID_HI 0x0503
+#define TS2_I2C_ENUM_REMOTE_MFGR_ID_LO 0x0504
+#define TS2_I2C_ENUM_REMOTE_MFGR_ID_HI_V1 0x0505
+#define TS2_I2C_ENUM_REMOTE_MFGR_ID_LO_V1 0x0506
+#define TS2_I2C_ENUM_REMOTE_PRODUCT_TYPE_HI 0x0505
+#define TS2_I2C_ENUM_REMOTE_PRODUCT_TYPE_LO 0x0506
+#define TS2_I2C_ENUM_REMOTE_PRODUCT_TYPE_HI_V1 0x0507
+#define TS2_I2C_ENUM_REMOTE_PRODUCT_TYPE_LO_V1 0x0508
+#define TS2_I2C_ENUM_REMOTE_SERNO_7 0x0507
+#define TS2_I2C_ENUM_REMOTE_SERNO_6 0x0508
+#define TS2_I2C_ENUM_REMOTE_SERNO_5 0x0509
+#define TS2_I2C_ENUM_REMOTE_SERNO_4 0x050A
+#define TS2_I2C_ENUM_REMOTE_SERNO_3 0x050B
+#define TS2_I2C_ENUM_REMOTE_SERNO_2 0x050C
+#define TS2_I2C_ENUM_REMOTE_SERNO_1 0x050D
+#define TS2_I2C_ENUM_REMOTE_SERNO_0 0x050E
+#define TS2_I2C_ENUM_REMOTE_ASSY_REV 0x050F
+#define TS2_I2C_ENUM_REMOTE_FW_VER_2 0x0510
+#define TS2_I2C_ENUM_REMOTE_FW_VER_1 0x0511
+#define TS2_I2C_ENUM_REMOTE_FW_VER_0 0x0512
+#define TS2_I2C_ENUM_REMOTE_VNODE_MIN_HI 0x0513
+#define TS2_I2C_ENUM_REMOTE_VNODE_MIN_LO 0x0514
+#define TS2_I2C_ENUM_REMOTE_VNODE_MAX_HI 0x0515
+#define TS2_I2C_ENUM_REMOTE_VNODE_MAX_LO 0x0516
+#define TS2_I2C_ENUM_REMOTE_INODE_MIN_HI 0x0517
+#define TS2_I2C_ENUM_REMOTE_INODE_MIN_LO 0x0518
+#define TS2_I2C_ENUM_REMOTE_INODE_MAX_HI 0x0519
+#define TS2_I2C_ENUM_REMOTE_INODE_MAX_LO 0x051A
+#define TS2_I2C_ENUM_REMOTE_TNODE_MIN 0x051B
+#define TS2_I2C_ENUM_REMOTE_TNODE_MAX 0x051C
+#define TS2_I2C_ENUM_REMOTE_POWER_MAX 0x051D
+
+
+
+#define TS2_I2C_ENUM_REMOTE_ACCE_0 0x0528
+#define TS2_I2C_ENUM_REMOTE_ACCE_1 0x0529
+#define TS2_I2C_ENUM_REMOTE_ACCE_2 0x052A
+#define TS2_I2C_ENUM_REMOTE_ACCE_3 0x052B
+#define TS2_I2C_ENUM_REMOTE_ACCE_4 0x052C
+#define TS2_I2C_ENUM_REMOTE_ACCE_5 0x052D
+#define TS2_I2C_ENUM_REMOTE_ACCE_6 0x052E
+#define TS2_I2C_ENUM_REMOTE_ACCE_7 0x052F
+#define TS2_I2C_ENUM_REMOTE_ACCE_8 0x0530
+#define TS2_I2C_ENUM_REMOTE_ACCE_9 0x0531
+#define TS2_I2C_ENUM_REMOTE_ACCE_10 0x0532
+#define TS2_I2C_ENUM_REMOTE_ACCE_11 0x0533
+#define TS2_I2C_ENUM_REMOTE_ACCE_12 0x0534
+#define TS2_I2C_ENUM_REMOTE_ACCE_13 0x0535
+#define TS2_I2C_ENUM_REMOTE_ACCE_14 0x0536
+#define TS2_I2C_ENUM_REMOTE_ACCE_15 0x0537
+
+
+/* page 0x06 */
+/* reserved */
+
+
+/* page 0x07 */
+/* voltage, current, temperature, power (puck only), pwm (puck only) */
+#define TS2_I2C_ID 0x0700
+#define TS2_I2C_ID_AIRBOARD_0 0x2c
+#define TS2_I2C_FLAGS_0 0x0701
+#define TS2_I2C_FLAGS_0_PUCK_PRIORITY (1<<0)
+#define TS2_I2C_FLAGS_1 0x0702
+#define TS2_I2C_FLAGS_2 0x0703
+#define TS2_I2C_FLAGS_2_A2A_CONNECT 0x80
+#define TS2_I2C_FLAGS_2_A2A_ATTEMPT 0x40
+#define TS2_I2C_FLAGS_2_BATTERY_DETECT 0x20
+#define TS2_I2C_FLAGS_2_WIRED_CHARGE 0x10
+#define TS2_I2C_FLAGS_2_PUCK_CHARGE 0x08
+#define TS2_I2C_FLAGS_2_WIRED 0x04
+#define TS2_I2C_FLAGS_2_PUCK 0x02
+#define TS2_I2C_FLAGS_2_PUCK_DETECT 0x01
+#define TS2_I2C_V_LOCAL 0x0704
+#define TS2_I2C_I_LOCAL 0x0705
+#define TS2_I2C_T_LOCAL 0x0706
+#define TS2_I2C_P_LOCAL 0x0707 /* puck only */
+#define TS2_I2C_V_REMOTE 0x0708
+#define TS2_I2C_I_REMOTE 0x0709
+#define TS2_I2C_T_REMOTE 0x070a
+#define TS2_I2C_P_REMOTE 0x070b /* puck only */
+#define TS2_I2C_PWM 0x070c /* puck only */
+#define TS2_I2C_PERIOD 0x070d /* puck only */
+
+/* config */
+#define TS2_I2C_TX_POWER_V1 0x0740
+#define TS2_I2C_TX_POWER_V2 0x0741
+
+/* puck only config */
+#define TS2_I2C_PWM_PERIOD 0x0780
+#define TS2_I2C_PWM_DUTY_ENUM 0x0781
+#define TS2_I2C_PWM_DUTY_MIN_RUN 0x0782
+#define TS2_I2C_PWM_DUTY_MIN_BOOST 0x0783
+#define TS2_I2C_PWM_DEADTIME 0x0784
+#define TS2_I2C_PWM_EFFICIENCY_OFFSET 0x0785
+#define TS2_I2C_PWM_EFFICIENCY_SCALE 0x0786
+
+/* airboard only */
+#define TS2_I2C_V_OFFSET 0x07c0
+#define TS2_I2C_WAKEUP_PERIOD 0x07c1
+#define TS2_I2C_CURRENT_ADJ_FOR_V1_SLOPE 0x07c2
+#define TS2_I2C_CURRENT_ADJ_FOR_V1_OFFSET 0x07c3
+#define TS2_I2C_VOLTAGE_ADJ_FOR_V1_SLOPE 0x07c4
+#define TS2_I2C_VOLTAGE_ADJ_FOR_V1_OFFSET 0x07c5
+
+/* misc registers */
+#define TS2_I2C_COMMAND 0x1000
+#define TS2_I2C_COMMAND_RESET_HOST 0x01
+#define TS2_I2C_COMMAND_CLEAR_BTN_SEQ_RESET_FLAG 0x02
+
+#define TS2_I2C_COMMAND_COMM_CONNECT 0x10
+#define TS2_I2C_COMMAND_COMM_DISCONNECT 0x11
+#define TS2_I2C_COMMAND_REENUMERATE 0x12
+
+#define TS2_I2C_COMMAND_FRAM_CHECKSUM_1400_READ 0x20
+#define TS2_I2C_COMMAND_FRAM_CHECKSUM_READ_1 0x21
+#define TS2_I2C_COMMAND_FRAM_CHECKSUM_READ_2 0x22
+
+#define TS2_I2C_COMMAND_MEM_READ_BYTE 0x80
+#define TS2_I2C_COMMAND_MEM_READ_CHAWMP 0x81
+#define TS2_I2C_COMMAND_MEM_WRITE_BYTE 0x82
+#define TS2_I2C_COMMAND_MEM_WRITE_CHAWMP 0x83
+#define TS2_I2C_COMMAND_PMIC_READ 0x84
+#define TS2_I2C_COMMAND_PMIC_WRITE 0x85
+#define TS2_I2C_COMMAND_SP_READ 0x86
+#define TS2_I2C_COMMAND_SP_INDIRECT_READ 0x87
+#define TS2_I2C_COMMAND_ADDR_MSB 0x1001
+#define TS2_I2C_COMMAND_ADDR_LSB 0x1002
+#define TS2_I2C_COMMAND_DATA_MSB 0x1003
+#define TS2_I2C_COMMAND_DATA_LSB 0x1004
+
+#define TS2_I2C_DEBUG_TRACE_DATA_RESERVED_1 0x101e /* reserved 1 */
+#define TS2_I2C_DEBUG_TRACE_DATA_RESERVED_2 0x101f /* reserved 2 */
+#define TS2_I2C_DEBUG_TRACE_DATA 0x1020 /* side-effect */
+
+#define A6_REG_TS2_I2C_INT_MASK_0 0
+#define A6_REG_TS2_I2C_INT_MASK_1 1
+#define A6_REG_TS2_I2C_INT_MASK_2 2
+#define A6_REG_TS2_I2C_INT_MASK_3 3
+#define A6_REG_TS2_I2C_INT_STATUS_0 4
+#define A6_REG_TS2_I2C_INT_STATUS_1 5
+#define A6_REG_TS2_I2C_INT_STATUS_2 6
+#define A6_REG_TS2_I2C_INT_STATUS_3 7
+#define A6_REG_TS2_I2C_BAT_STATUS 8
+#define A6_REG_TS2_I2C_BAT_RARC 9
+#define A6_REG_TS2_I2C_BAT_RSRC 10
+#define A6_REG_TS2_I2C_BAT_AVG_CUR_LSB_MSB 11
+#define A6_REG_TS2_I2C_BAT_TEMP_LSB_MSB 12
+#define A6_REG_TS2_I2C_BAT_VOLT_LSB_MSB 13
+#define A6_REG_TS2_I2C_BAT_CUR_LSB_MSB 14
+#define A6_REG_TS2_I2C_BAT_COULOMB_LSB_MSB 15
+// #define A6_REG_TS2_I2C_BAT_AS 16 // two entries? TODO
+#define A6_REG_TS2_I2C_BAT_FULL_LSB_MSB 17
+#define A6_REG_TS2_I2C_BAT_FULL40_LSB_MSB 18
+#define A6_REG_TS2_I2C_BAT_RSNSP 19
+#define A6_REG_TS2_I2C_BAT_ROMID_0 20
+#define A6_REG_TS2_I2C_BAT_COMMAND_STATUS 21
+#define A6_REG_TS2_I2C_BAT_TEMP_LOW_LSB_MSB 22
+#define A6_REG_TS2_I2C_BAT_TEMP_HIGH_LSB_MSB 23
+#define A6_REG_TS2_I2C_BAT_VOLT_LOW_LSB_MSB 24
+#define A6_REG_TS2_I2C_BAT_RARC_CRIT 25
+#define A6_REG_TS2_I2C_BAT_RARC_LOW_2 26
+#define A6_REG_TS2_I2C_BAT_RARC_LOW_1 27
+#define A6_REG_TS2_I2C_BAT_RAAC_MSB 28
+#define A6_REG_TS2_I2C_ID 29
+#define A6_REG_TS2_I2C_FLAGS_0 30
+#define A6_REG_TS2_I2C_FLAGS_2 31
+#define A6_REG_VERSION 32
+#define A6_REG_TS2_I2C_V_OFFSET 33
+#define A6_REG_TS2_I2C_WAKEUP_PERIOD 34
+#define A6_REG_TS2_I2C_COMMAND 35
+#define A6_REG_REMOTE_VERSION 36
+#define A6_REG_ACCESSORY_DATA_0 37
+#define A6_REG_ACCESSORY_DATA_1 38
+#define A6_REG_ACCESSORY_DATA_2 39
+#define A6_REG_ACCESSORY_DATA_3 40
+#define A6_REG_ACCESSORY_DATA_4 41
+#define A6_REG_ACCESSORY_DATA_5 42
+#define A6_REG_ACCESSORY_DATA_6 43
+#define A6_REG_ACCESSORY_DATA_7 44
+#define A6_REG_ACCESSORY_DATA_8 45
+#define A6_REG_ACCESSORY_DATA_9 46
+#define A6_REG_ACCESSORY_DATA_10 47
+#define A6_REG_ACCESSORY_DATA_11 48
+#define A6_REG_ACCESSORY_DATA_12 49
+#define A6_REG_REMOTE_ACCESSORY_DATA_0 50
+#define A6_REG_REMOTE_ACCESSORY_DATA_1 51
+#define A6_REG_REMOTE_ACCESSORY_DATA_2 52
+#define A6_REG_REMOTE_ACCESSORY_DATA_3 53
+#define A6_REG_REMOTE_ACCESSORY_DATA_4 54
+#define A6_REG_REMOTE_ACCESSORY_DATA_5 55
+#define A6_REG_REMOTE_ACCESSORY_DATA_6 56
+#define A6_REG_REMOTE_ACCESSORY_DATA_7 57
+#define A6_REG_REMOTE_ACCESSORY_DATA_8 58
+#define A6_REG_REMOTE_ACCESSORY_DATA_9 59
+#define A6_REG_REMOTE_ACCESSORY_DATA_10 60
+#define A6_REG_REMOTE_ACCESSORY_DATA_11 61
+#define A6_REG_REMOTE_ACCESSORY_DATA_12 62
+#define A6_REG_TS2_I2C_COMM_STATUS 63
+#define A6_REG_TS2_I2C_COMM_TXDATA_RXDATA 64
+#define A6_REG_TS2_I2C_BAT_SACR_LSB_MSB 65
+#define A6_REG_TS2_I2C_BAT_ASL 66
+// #define A6_REG_TS2_I2C_BAT_AS 67 // two entries? TODO
+#define A6_REG_TS2_I2C_BAT_FAC_LSB_MSB 68
+#define A6_REG_MAX_POWER_AVAILABLE 69
+#define A6_REG_TS2_I2C_ENUM_REMOTE_STRUCT_VER 70
+#define A6_REG_ACCESSORY_DATA_13 71
+#define A6_REG_ACCESSORY_DATA_14 72
+#define A6_REG_ACCESSORY_DATA_15 73
+#define A6_REG_REMOTE_ACCESSORY_DATA_13 74
+#define A6_REG_REMOTE_ACCESSORY_DATA_14 75
+#define A6_REG_REMOTE_ACCESSORY_DATA_15 76
+#define A6_REG_TS2_I2C_ENUM_MIN_PWM 77
+#define A6_REG_ACCESSORY_DATA_COMBO 78
+#define A6_REG_REMOTE_ACCESSORY_DATA_COMBO 79
+#define A6_REG_TS2_I2C_COMMAND_ADDR_LSB_MSB 80
+#define A6_REG_TS2_I2C_COMMAND_DATA_LSB_MSB 81
+#define A6_REG_REMOTE_MFGRID_V1 82
+#define A6_REG_REMOTE_MFGRID_V2 83
+#define A6_REG_REMOTE_PRODUCTID_V1 84
+#define A6_REG_REMOTE_PRODUCTID_V2 85
+#define A6_REG_REMOTE_SERNO_V1 86
+#define A6_REG_REMOTE_SERNO_V2 87
+#define A6_REG_LOCAL_MFGRID 88
+#define A6_REG_LOCAL_PRODUCTID 89
+#define A6_REG_LOCAL_SERNO 90
+
+
+#define RSENSE_DEFAULT 20;
+#define FORCE_WAKE_TIMER_EXPIRY (HZ/20)
+
+#define a6_wait_event_ex(wq, condition) \
+do { \
+ if (condition) \
+ break; \
+ do { \
+ DEFINE_WAIT(__wait); \
+ \
+ for (;;) { \
+ prepare_to_wait_exclusive(&wq, &__wait, \
+ TASK_UNINTERRUPTIBLE); \
+ if (condition) \
+ break; \
+ schedule(); \
+ } \
+ finish_wait(&wq, &__wait); \
+ } while (0); \
+} while (0)
+
+
+enum {
+ DEVICE_BUSY_BIT = 0,
+ IS_OPENED,
+ IS_INITIALIZED_BIT,
+ BOOTLOAD_ACTIVE_BIT,
+ FORCE_WAKE_ACTIVE_BIT,
+ READ_ACTIVE_BIT,
+ WRITE_ACTIVE_BIT,
+ A2A_CONNECTED,
+ EXTRACT_INITIATED,
+ // capabilities
+ CAP_PERIODIC_WAKE,
+#ifdef A6_PQ
+ STARTING_AID_TASK,
+ KILLING_AID_TASK,
+ IS_QUIESCED,
+#endif
+ IS_SUSPENDED,
+ INT_PENDING,
+ SIZE_FLAGS
+};
+
+struct a6_device_state {
+ struct i2c_client *i2c_dev;
+ struct a6_platform_data *plat_data;
+ struct file_operations fops;
+ struct miscdevice mdev;
+
+ struct mutex dev_mutex;
+ unsigned int timestamping;
+
+ struct timer_list a6_force_wake_timer;
+ struct work_struct a6_force_wake_work;
+ struct mutex a6_force_wake_mutex;
+
+ wait_queue_head_t dev_busyq;
+ struct work_struct a6_irq_work;
+
+ int32_t cpufreq_hold_flag;
+ struct workqueue_struct* ka6d_workqueue;
+ struct workqueue_struct* ka6d_fw_workqueue;
+
+ uint32_t cached_rsense_val: 16;
+ uint32_t busy_count: 8;
+ DECLARE_BITMAP(flags, SIZE_FLAGS);
+
+#ifdef A6_PQ
+ struct completion aq_enq_complete;
+ struct completion aid_exit_complete;
+ struct mutex aq_mutex;
+ struct list_head aq_head;
+ struct task_struct* ai_dispatch_task;
+#ifdef A6_DEBUG
+ uint32_t dbgflg_kill_raid: 1;
+
+ uint8_t debug_restart_aid;
+ uint8_t debug_flush_aiq;
+ uint8_t debug_unused_01;
+ uint8_t debug_unused_02;
+#endif
+#endif
+
+ int cpufreq_hold;
+
+ // pmem extract
+ struct file_operations pmem_fops;
+ struct miscdevice pmem_mdev;
+ char pmem_dev_name[16];
+
+ char *a2a_rd_buf, *a2a_wr_buf;
+ char *a2a_rp, *a2a_wp;
+
+ enum chg_type otg_chg_type;
+ struct delayed_work charge_work;
+ struct delayed_work init_connected_ps_work;
+ int stop_heartbeat;
+ int last_percent;
+
+ struct switch_dev *dock_switch;
+};
+
+struct a6_device_state *batt_state;
+
+#ifdef A6_PQ
+int32_t a6_start_ai_dispatch_task(struct a6_device_state* state);
+int32_t a6_stop_ai_dispatch_task(struct a6_device_state* state);
+int32_t flush_a6_action_items(struct a6_device_state* state);
+#endif
+
+static ssize_t a6_generic_show(struct device *dev, struct device_attribute *dev_attr, char *buf);
+static ssize_t a6_generic_store(struct device *dev, struct device_attribute *dev_attr, const char *buf,
+ size_t count);
+
+typedef int32_t (*format_show_fn)(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer);
+typedef int32_t (*format_store_fn)(const struct a6_device_state* state, const uint8_t* fmt_buffer,
+ uint8_t* val, uint32_t size_buffer);
+
+int32_t format_current(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer);
+int32_t format_voltage(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer);
+int32_t r_format_voltage(const struct a6_device_state* state, const uint8_t* fmt_buffer,
+ uint8_t* val, uint32_t size_buffer);
+int32_t format_rawcoulomb(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer);
+int32_t format_coulomb(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer);
+int32_t format_age(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer);
+int32_t format_fullx(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer);
+int32_t format_temp(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer);
+int32_t r_format_temp(const struct a6_device_state* state, const uint8_t* fmt_buffer,
+ uint8_t* val, uint32_t size_buffer);
+int32_t format_status(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer);
+int32_t format_rsense(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer);
+int32_t format_charge_source_status(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer);
+int32_t format_version(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer);
+int32_t format_v_offset(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer);
+int32_t format_command(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer);
+int32_t format_accessory(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer);
+int32_t format_comm_status(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer);
+int32_t format_raw_unsigned(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer);
+int32_t format_max_power_available(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer);
+int32_t format_accessory_combo(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer);
+int32_t format_u16_hex(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer);
+int32_t format_serno_v1(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer);
+int32_t format_serno_v2(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer);
+
+static ssize_t a6_diag_show(struct device *dev, struct device_attribute *dev_attr, char *buf);
+static ssize_t a6_diag_store(struct device *dev, struct device_attribute *dev_attr, const char *buf,
+ size_t count);
+static ssize_t a6_val_cksum_show(struct device *dev, struct device_attribute *attr, char *buf);
+
+static void a6_dock_update_state(struct a6_device_state *state);
+
+static void a6_update_connected_ps(void);
+
+void a6_force_wake_timer_callback(ulong data);
+/*
+ Note: 16-bit accesses comprising an LSB, MSB register pair must be specified in LSB:MSB order
+ in the corresponding a6_register_desc struct. This is because an a6-fw constraint
+ restricts 16-bit value accesses to MSB-leading only. The a6_i2c_read_reg and the
+ a6_i2c_write_reg functions take the LSB:MSB definition in the a6_register_desc struct
+ and re-orders the i2c txn to read in MSB:LSB order.
+*/
+struct a6_register_desc {
+#define id_size 20
+
+ const char* debug_name;
+ struct device_attribute dev_attr;
+ format_show_fn format;
+ format_store_fn r_format;
+ uint16_t id[id_size];
+ uint32_t num_ids: 5;
+ uint32_t ro: 1;
+} a6_register_desc_arr[] = {
+ /* interrupt control registers */
+ [0] = { .debug_name = "TS2_I2C_INT_MASK_0",
+ {{.name = "int_mask0", .mode = S_IRUGO | S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .id = {TS2_I2C_INT_MASK_0}, .num_ids = 1, .ro = 0},
+ [1] = { .debug_name = "TS2_I2C_INT_MASK_1",
+ {{.name = "int_mask1", .mode = S_IRUGO | S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .id = {TS2_I2C_INT_MASK_1}, .num_ids = 1, .ro = 0},
+ [2] = { .debug_name = "TS2_I2C_INT_MASK_2",
+ {{.name = "int_mask2", .mode = S_IRUGO | S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .id = {TS2_I2C_INT_MASK_2}, .num_ids = 1, .ro = 0},
+ [3] = { .debug_name = "TS2_I2C_INT_MASK_3",
+ {{.name = "int_mask3", .mode = S_IRUGO | S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .id = {TS2_I2C_INT_MASK_3}, .num_ids = 1, .ro = 0},
+ [4] = { .debug_name = "TS2_I2C_INT_STATUS_0",
+ {{.name = "int_status0", .mode = S_IRUGO | S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .id = {TS2_I2C_INT_STATUS_0}, .num_ids = 1, .ro = 0},
+ [5] = { .debug_name = "TS2_I2C_INT_STATUS_1",
+ {{.name = "int_status1", .mode = S_IRUGO | S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .id = {TS2_I2C_INT_STATUS_1}, .num_ids = 1, .ro = 0},
+ [6] = { .debug_name = "TS2_I2C_INT_STATUS_2",
+ {{.name = "int_status2", .mode = S_IRUGO | S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .id = {TS2_I2C_INT_STATUS_2}, .num_ids = 1, .ro = 0},
+ [7] = { .debug_name = "TS2_I2C_INT_STATUS_3",
+ {{.name = "int_status3", .mode = S_IRUGO | S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .id = {TS2_I2C_INT_STATUS_3}, .num_ids = 1, .ro = 0},
+
+ /* battery control registers */
+ [8] = { .debug_name = "TS2_I2C_BAT_STATUS",
+ {{.name = "status", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_status,
+ .id = {TS2_I2C_BAT_STATUS}, .num_ids = 1, .ro = 1},
+ [9] = { .debug_name = "TS2_I2C_BAT_RARC",
+ {{.name = "getpercent", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .id = {TS2_I2C_BAT_RARC}, .num_ids = 1, .ro = 1},
+ [10] = { .debug_name = "TS2_I2C_BAT_RSRC",
+ {{.name = "rsrc", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .id = {TS2_I2C_BAT_RSRC}, .num_ids = 1, .ro = 1},
+ [11] = { .debug_name = "TS2_I2C_BAT_AVG_CUR_LSB_MSB",
+ {{.name = "getavgcurrent", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_current,
+ .id = {TS2_I2C_BAT_AVG_CUR_LSB, TS2_I2C_BAT_AVG_CUR_MSB},
+ .num_ids = 2, .ro = 1},
+ [12] = { .debug_name = "TS2_I2C_BAT_TEMP_LSB_MSB",
+ {{.name = "gettemp", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_temp,
+ .id = {TS2_I2C_BAT_TEMP_LSB, TS2_I2C_BAT_TEMP_MSB},
+ .num_ids = 2, .ro = 1},
+ [13] = { .debug_name = "TS2_I2C_BAT_VOLT_LSB_MSB",
+ {{.name = "getvoltage", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_voltage,
+ .id = {TS2_I2C_BAT_VOLT_LSB, TS2_I2C_BAT_VOLT_MSB},
+ .num_ids = 2, .ro = 1},
+ [14] = { .debug_name = "TS2_I2C_BAT_CUR_LSB_MSB",
+ {{.name = "getcurrent", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_current,
+ .id = {TS2_I2C_BAT_CUR_LSB, TS2_I2C_BAT_CUR_MSB},
+ .num_ids = 2, .ro = 1},
+ [15] = { .debug_name = "TS2_I2C_BAT_COULOMB_LSB_MSB",
+ {{.name = "getrawcoulomb", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_rawcoulomb,
+ .id = {TS2_I2C_BAT_COULOMB_LSB, TS2_I2C_BAT_COULOMB_MSB},
+ .num_ids = 2, .ro = 1},
+ [16] = { .debug_name = "TS2_I2C_BAT_AS",
+ {{.name = "getage", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .format = format_age,
+ .id = {TS2_I2C_BAT_AS}, .num_ids = 1, .ro = 0},
+ [17] = { .debug_name = "TS2_I2C_BAT_FULL_LSB_MSB",
+ {{.name = "getfull", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_fullx,
+ .id = {TS2_I2C_BAT_FULL_LSB, TS2_I2C_BAT_FULL_MSB},
+ .num_ids = 2, .ro = 1},
+ [18] = {.debug_name = "TS2_I2C_BAT_FULL40_LSB_MSB",
+ {{.name = "getfull40", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_fullx,
+ .id = {TS2_I2C_BAT_FULL40_LSB, TS2_I2C_BAT_FULL40_MSB},
+ .num_ids = 2, .ro = 1},
+ [19] = {.debug_name = "TS2_I2C_BAT_RSNSP",
+ {{.name = "getrsense", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_rsense,
+ .id = {TS2_I2C_BAT_RSNSP}, .num_ids = 1, .ro = 1},
+
+ [20] = {.debug_name = "TS2_I2C_BAT_ROMID_0",
+ {{.name = "romid_0", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .id = {TS2_I2C_BAT_ROMID_0}, .num_ids = 1, .ro = 1},
+
+ [21] = {.debug_name = "TS2_I2C_BAT_COMMAND_STATUS",
+ {{.name = "command_status", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .id = {TS2_I2C_BAT_COMMAND_STATUS}, .num_ids = 1, .ro = 0},
+
+
+ [22] = {.debug_name = "TS2_I2C_BAT_TEMP_LOW_LSB_MSB",
+ {{.name = "temp_low", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .format = format_temp,
+ .r_format = r_format_temp,
+ .id = {TS2_I2C_BAT_TEMP_LOW_LSB, TS2_I2C_BAT_TEMP_LOW_MSB},
+ .num_ids = 2, .ro = 0},
+ [23] = {.debug_name = "TS2_I2C_BAT_TEMP_HIGH_LSB_MSB",
+ {{.name = "temp_high", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .format = format_temp,
+ .r_format = r_format_temp,
+ .id = {TS2_I2C_BAT_TEMP_HIGH_LSB, TS2_I2C_BAT_TEMP_HIGH_MSB},
+ .num_ids = 2, .ro = 0},
+ [24] = {.debug_name = "TS2_I2C_BAT_VOLT_LOW_LSB_MSB",
+ {{.name = "volt_low", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .format = format_voltage,
+ .r_format = r_format_voltage,
+ .id = {TS2_I2C_BAT_VOLT_LOW_LSB, TS2_I2C_BAT_VOLT_LOW_MSB},
+ .num_ids = 2, .ro = 0},
+ [25] = {.debug_name = "TS2_I2C_BAT_RARC_CRIT",
+ {{.name = "rarc_crit", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .id = {TS2_I2C_BAT_RARC_CRIT}, .num_ids = 1, .ro = 0},
+ [26] = {.debug_name = "TS2_I2C_BAT_RARC_LOW_2",
+ {{.name = "rarc_low_2", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .id = {TS2_I2C_BAT_RARC_LOW_2}, .num_ids = 1, .ro = 0},
+ [27] = {.debug_name = "TS2_I2C_BAT_RARC_LOW_1",
+ {{.name = "rarc_low_1", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .id = {TS2_I2C_BAT_RARC_LOW_1}, .num_ids = 1, .ro = 0},
+ [28] = {.debug_name = "TS2_I2C_BAT_RAAC_MSB",
+ {{.name = "getcoulomb", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .format = format_coulomb,
+ .id = {TS2_I2C_BAT_RAAC_LSB, TS2_I2C_BAT_RAAC_MSB},
+ .num_ids = 2, .ro = 0},
+
+ /* puck registers */
+ [29] = {.debug_name = "TS2_I2C_ID",
+ {{.name = "id", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .id = {TS2_I2C_ID}, .num_ids = 1, .ro = 1},
+ [30] = {.debug_name = "TS2_I2C_FLAGS_0",
+ {{.name = "puck_priority", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .id = {TS2_I2C_FLAGS_0}, .num_ids = 1, .ro = 0},
+ [31] = {.debug_name = "TS2_I2C_FLAGS_2",
+ {{.name = "charger", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_charge_source_status,
+ .id = {TS2_I2C_FLAGS_2}, .num_ids = 1, .ro = 1},
+
+ /* enumeration registers */
+ [32] = {.debug_name = "VERSION",
+ {{.name = "getversion", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_version,
+ .id = { TS2_I2C_ENUM_MFGR_ID_LO, TS2_I2C_ENUM_MFGR_ID_HI,
+ TS2_I2C_ENUM_PRODUCT_TYPE_LO, TS2_I2C_ENUM_PRODUCT_TYPE_HI,
+ TS2_I2C_ENUM_SERNO_0, TS2_I2C_ENUM_SERNO_1,
+ TS2_I2C_ENUM_SERNO_2, TS2_I2C_ENUM_SERNO_3,
+ TS2_I2C_ENUM_SERNO_4, TS2_I2C_ENUM_SERNO_5,
+ TS2_I2C_ENUM_SERNO_6, TS2_I2C_ENUM_SERNO_7,
+ TS2_I2C_ENUM_ASSY_REV, TS2_I2C_ENUM_FW_VER_0,
+ TS2_I2C_ENUM_FW_VER_1, TS2_I2C_ENUM_FW_VER_2},
+ .num_ids = 16, .ro = 1},
+
+ /* puck registers */
+ [33] = {.debug_name = "TS2_I2C_V_OFFSET",
+ {{.name = "v_offset", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .format = format_v_offset,
+ .id = {TS2_I2C_V_OFFSET}, .num_ids = 1, .ro = 0},
+
+ /* self-wake registers */
+ [34] = {.debug_name = "TS2_I2C_WAKEUP_PERIOD",
+ {{.name = "periodic_wake_bit_params", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .id = {TS2_I2C_WAKEUP_PERIOD}, .num_ids = 1, .ro = 0},
+
+ /* command registers */
+ [35] = {.debug_name = "TS2_I2C_COMMAND",
+ {{.name = "command", .mode = S_IWUGO},
+ .show = NULL, .store = a6_generic_store},
+ .id = {TS2_I2C_COMMAND}, .num_ids = 1, .ro = 0},
+
+ /* remote enumeration registers */
+ [36] = {.debug_name = "REMOTE_VERSION",
+ {{.name = "getremoteversion", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_version,
+ .id = { TS2_I2C_ENUM_REMOTE_MFGR_ID_LO, TS2_I2C_ENUM_REMOTE_MFGR_ID_HI,
+ TS2_I2C_ENUM_REMOTE_PRODUCT_TYPE_LO, TS2_I2C_ENUM_REMOTE_PRODUCT_TYPE_HI,
+ TS2_I2C_ENUM_REMOTE_SERNO_0, TS2_I2C_ENUM_REMOTE_SERNO_1,
+ TS2_I2C_ENUM_REMOTE_SERNO_2, TS2_I2C_ENUM_REMOTE_SERNO_3,
+ TS2_I2C_ENUM_REMOTE_SERNO_4, TS2_I2C_ENUM_REMOTE_SERNO_5,
+ TS2_I2C_ENUM_REMOTE_SERNO_6, TS2_I2C_ENUM_REMOTE_SERNO_7,
+ TS2_I2C_ENUM_REMOTE_ASSY_REV, TS2_I2C_ENUM_REMOTE_FW_VER_0,
+ TS2_I2C_ENUM_REMOTE_FW_VER_1, TS2_I2C_ENUM_REMOTE_FW_VER_2},
+ .num_ids = 16, .ro = 1},
+
+ /* accessory data registers */
+ [37] = {.debug_name = "ACCESSORY_DATA_0",
+ {{.name = "acc_data_0", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_ACCE_0}, .num_ids = 1, .ro = 0},
+
+ [38] = {.debug_name = "ACCESSORY_DATA_1",
+ {{.name = "acc_data_1", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_ACCE_1}, .num_ids = 1, .ro = 0},
+
+ [39] = {.debug_name = "ACCESSORY_DATA_2",
+ {{.name = "acc_data_2", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_ACCE_2}, .num_ids = 1, .ro = 0},
+
+ [40] = {.debug_name = "ACCESSORY_DATA_3",
+ {{.name = "acc_data_3", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_ACCE_3}, .num_ids = 1, .ro = 0},
+
+ [41] = {.debug_name = "ACCESSORY_DATA_4",
+ {{.name = "acc_data_4", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_ACCE_4}, .num_ids = 1, .ro = 0},
+
+ [42] = {.debug_name = "ACCESSORY_DATA_5",
+ {{.name = "acc_data_5", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_ACCE_5}, .num_ids = 1, .ro = 0},
+
+ [43] = {.debug_name = "ACCESSORY_DATA_6",
+ {{.name = "acc_data_6", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_ACCE_6}, .num_ids = 1, .ro = 0},
+
+ [44] = {.debug_name = "ACCESSORY_DATA_7",
+ {{.name = "acc_data_7", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_ACCE_7}, .num_ids = 1, .ro = 0},
+
+ [45] = {.debug_name = "ACCESSORY_DATA_8",
+ {{.name = "acc_data_8", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_ACCE_8}, .num_ids = 1, .ro = 0},
+
+ [46] = {.debug_name = "ACCESSORY_DATA_9",
+ {{.name = "acc_data_9", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_ACCE_9}, .num_ids = 1, .ro = 0},
+
+ [47] = {.debug_name = "ACCESSORY_DATA_10",
+ {{.name = "acc_data_10", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_ACCE_10}, .num_ids = 1, .ro = 0},
+
+ [48] = {.debug_name = "ACCESSORY_DATA_11",
+ {{.name = "acc_data_11", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_ACCE_11}, .num_ids = 1, .ro = 0},
+
+ [49] = {.debug_name = "ACCESSORY_DATA_12",
+ {{.name = "acc_data_12", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_ACCE_12}, .num_ids = 1, .ro = 0},
+
+ /* remote accessory data registers */
+ [50] = {.debug_name = "REMOTE_ACCESSORY_DATA_0",
+ {{.name = "remote_acc_data_0", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_REMOTE_ACCE_0}, .num_ids = 1, .ro = 1},
+
+ [51] = {.debug_name = "REMOTE_ACCESSORY_DATA_1",
+ {{.name = "remote_acc_data_1", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_REMOTE_ACCE_1}, .num_ids = 1, .ro = 1},
+
+ [52] = {.debug_name = "REMOTE_ACCESSORY_DATA_2",
+ {{.name = "remote_acc_data_2", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_REMOTE_ACCE_2}, .num_ids = 1, .ro = 1},
+
+ [53] = {.debug_name = "REMOTE_ACCESSORY_DATA_3",
+ {{.name = "remote_acc_data_3", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_REMOTE_ACCE_3}, .num_ids = 1, .ro = 1},
+
+ [54] = {.debug_name = "REMOTE_ACCESSORY_DATA_4",
+ {{.name = "remote_acc_data_4", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_REMOTE_ACCE_4}, .num_ids = 1, .ro = 1},
+
+ [55] = {.debug_name = "REMOTE_ACCESSORY_DATA_5",
+ {{.name = "remote_acc_data_5", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_REMOTE_ACCE_5}, .num_ids = 1, .ro = 1},
+
+ [56] = {.debug_name = "REMOTE_ACCESSORY_DATA_6",
+ {{.name = "remote_acc_data_6", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_REMOTE_ACCE_6}, .num_ids = 1, .ro = 1},
+
+ [57] = {.debug_name = "REMOTE_ACCESSORY_DATA_7",
+ {{.name = "remote_acc_data_7", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_REMOTE_ACCE_7}, .num_ids = 1, .ro = 1},
+
+ [58] = {.debug_name = "REMOTE_ACCESSORY_DATA_8",
+ {{.name = "remote_acc_data_8", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_REMOTE_ACCE_8}, .num_ids = 1, .ro = 1},
+
+ [59] = {.debug_name = "REMOTE_ACCESSORY_DATA_9",
+ {{.name = "remote_acc_data_9", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_REMOTE_ACCE_9}, .num_ids = 1, .ro = 1},
+
+ [60] = {.debug_name = "REMOTE_ACCESSORY_DATA_10",
+ {{.name = "remote_acc_data_10", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_REMOTE_ACCE_10}, .num_ids = 1, .ro = 1},
+
+ [61] = {.debug_name = "REMOTE_ACCESSORY_DATA_11",
+ {{.name = "remote_acc_data_11", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_REMOTE_ACCE_11}, .num_ids = 1, .ro = 1},
+
+ [62] = {.debug_name = "REMOTE_ACCESSORY_DATA_12",
+ {{.name = "remote_acc_data_12", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_REMOTE_ACCE_12}, .num_ids = 1, .ro = 1},
+
+ [63] = {.debug_name = "TS2_I2C_COMM_STATUS",
+ {{.name = "get_comm_status", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_comm_status,
+ .id = {TS2_I2C_COMM_STATUS}, .num_ids = 1, .ro = 1},
+
+ [64] = {.debug_name = "TS2_I2C_COMM_TXDATA_RXDATA",
+ {{.name = "comm_txdata_rx_data", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .format = format_comm_status,
+ .id = {TS2_I2C_COMM_TXDATA_RXDATA}, .num_ids = 1, .ro = 0},
+
+ [65] = { .debug_name = "TS2_I2C_BAT_SACR_LSB_MSB",
+ {{.name = "getsacr", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_rawcoulomb,
+ .id = {TS2_I2C_BAT_SACR_LSB, TS2_I2C_BAT_SACR_MSB},
+ .num_ids = 2, .ro = 0},
+
+ [66] = { .debug_name = "TS2_I2C_BAT_ASL",
+ {{.name = "getasl", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_raw_unsigned,
+ .id = {TS2_I2C_BAT_ASL}, .num_ids = 1, .ro = 0},
+
+ [67] = { .debug_name = "TS2_I2C_BAT_AS",
+ {{.name = "getrawas", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_raw_unsigned,
+ .id = {TS2_I2C_BAT_AS}, .num_ids = 1, .ro = 0},
+
+ [68] = { .debug_name = "TS2_I2C_BAT_FAC_LSB_MSB",
+ {{.name = "getfac", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_rawcoulomb,
+ .id = {TS2_I2C_BAT_FAC_LSB, TS2_I2C_BAT_FAC_MSB},
+ .num_ids = 2, .ro = 0},
+
+ [69] = {.debug_name = "MAX_POWER_AVAILABLE",
+ {{.name = "getmaxpoweravail", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_max_power_available,
+ .id = { TS2_I2C_ENUM_REMOTE_VNODE_MAX_LO, TS2_I2C_ENUM_REMOTE_VNODE_MAX_HI,
+ TS2_I2C_ENUM_REMOTE_INODE_MAX_LO, TS2_I2C_ENUM_REMOTE_INODE_MAX_HI,
+ TS2_I2C_ENUM_REMOTE_POWER_MAX},
+ .num_ids = 5, .ro = 1},
+
+ [70] = {.debug_name = "TS2_I2C_ENUM_REMOTE_STRUCT_VER",
+ {{.name = "remote_struct_ver", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .id = {TS2_I2C_ENUM_REMOTE_STRUCT_VER}, .num_ids = 1, .ro = 1},
+
+ [71] = {.debug_name = "ACCESSORY_DATA_13",
+ {{.name = "acc_data_13", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_ACCE_13}, .num_ids = 1, .ro = 0},
+
+ [72] = {.debug_name = "ACCESSORY_DATA_14",
+ {{.name = "acc_data_14", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_ACCE_14}, .num_ids = 1, .ro = 0},
+
+ [73] = {.debug_name = "ACCESSORY_DATA_15",
+ {{.name = "acc_data_15", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_ACCE_15}, .num_ids = 1, .ro = 0},
+
+ [74] = {.debug_name = "REMOTE_ACCESSORY_DATA_13",
+ {{.name = "remote_acc_data_13", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_REMOTE_ACCE_13}, .num_ids = 1, .ro = 1},
+
+ [75] = {.debug_name = "REMOTE_ACCESSORY_DATA_14",
+ {{.name = "remote_acc_data_14", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_REMOTE_ACCE_14}, .num_ids = 1, .ro = 1},
+
+ [76] = {.debug_name = "REMOTE_ACCESSORY_DATA_15",
+ {{.name = "remote_acc_data_15", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_accessory,
+ .id = { TS2_I2C_ENUM_REMOTE_ACCE_15}, .num_ids = 1, .ro = 1},
+
+ [77] = {.debug_name = "TS2_I2C_ENUM_MIN_PWM",
+ {{.name = "min_pwm", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .id = { TS2_I2C_ENUM_MIN_PWM}, .num_ids = 1, .ro = 0},
+
+ [78] = {.debug_name = "ACCESSORY_DATA_COMBO",
+ {{.name = "acc_data_combo", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .format = format_accessory_combo,
+ .id = { TS2_I2C_ENUM_ACCE_0, TS2_I2C_ENUM_ACCE_1, TS2_I2C_ENUM_ACCE_2,
+ TS2_I2C_ENUM_ACCE_3, TS2_I2C_ENUM_ACCE_4, TS2_I2C_ENUM_ACCE_5,
+ TS2_I2C_ENUM_ACCE_6, TS2_I2C_ENUM_ACCE_7, TS2_I2C_ENUM_ACCE_8,
+ TS2_I2C_ENUM_ACCE_9, TS2_I2C_ENUM_ACCE_10, TS2_I2C_ENUM_ACCE_11,
+ TS2_I2C_ENUM_ACCE_12, TS2_I2C_ENUM_ACCE_13, TS2_I2C_ENUM_ACCE_14,
+ TS2_I2C_ENUM_ACCE_15},
+ .num_ids = 16, .ro = 0},
+
+ [79] = {.debug_name = "REMOTE_ACCESSORY_DATA_COMBO",
+ {{.name = "remote_acc_data_combo", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_accessory_combo,
+ .id = { TS2_I2C_ENUM_REMOTE_ACCE_0, TS2_I2C_ENUM_REMOTE_ACCE_1,
+ TS2_I2C_ENUM_REMOTE_ACCE_2, TS2_I2C_ENUM_REMOTE_ACCE_3,
+ TS2_I2C_ENUM_REMOTE_ACCE_4, TS2_I2C_ENUM_REMOTE_ACCE_5,
+ TS2_I2C_ENUM_REMOTE_ACCE_6, TS2_I2C_ENUM_REMOTE_ACCE_7,
+ TS2_I2C_ENUM_REMOTE_ACCE_8, TS2_I2C_ENUM_REMOTE_ACCE_9,
+ TS2_I2C_ENUM_REMOTE_ACCE_10, TS2_I2C_ENUM_REMOTE_ACCE_11,
+ TS2_I2C_ENUM_REMOTE_ACCE_12, TS2_I2C_ENUM_REMOTE_ACCE_13,
+ TS2_I2C_ENUM_REMOTE_ACCE_14, TS2_I2C_ENUM_REMOTE_ACCE_15},
+ .num_ids = 16, .ro = 1},
+
+ [80] = {.debug_name = "TS2_I2C_COMMAND_ADDR_LSB_MSB",
+ {{.name = "command_addr", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .id = {TS2_I2C_COMMAND_ADDR_LSB, TS2_I2C_COMMAND_ADDR_MSB},
+ .format = format_u16_hex,
+ .num_ids = 2, .ro = 0},
+ [81] = {.debug_name = "TS2_I2C_COMMAND_DATA_LSB_MSB",
+ {{.name = "command_data", .mode = S_IRUGO|S_IWUGO},
+ .show = a6_generic_show, .store = a6_generic_store},
+ .id = {TS2_I2C_COMMAND_DATA_LSB, TS2_I2C_COMMAND_DATA_MSB},
+ .format = format_u16_hex,
+ .num_ids = 2, .ro = 0},
+
+ [82] = {.debug_name = "REMOTE_MFGRID_V1",
+ {{.name = "remote_mfgrid_v1", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_u16_hex,
+ .id = { TS2_I2C_ENUM_REMOTE_MFGR_ID_LO_V1,
+ TS2_I2C_ENUM_REMOTE_MFGR_ID_HI_V1},
+ .num_ids = 2, .ro = 1},
+ [83] = {.debug_name = "REMOTE_MFGRID_V2",
+ {{.name = "remote_mfgrid_v2", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_u16_hex,
+ .id = { TS2_I2C_ENUM_REMOTE_MFGR_ID_LO,
+ TS2_I2C_ENUM_REMOTE_MFGR_ID_HI},
+ .num_ids = 2, .ro = 1},
+ [84] = {.debug_name = "REMOTE_PRODUCTID_V1",
+ {{.name = "remote_productid_v1", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_u16_hex,
+ .id = { TS2_I2C_ENUM_REMOTE_PRODUCT_TYPE_LO_V1,
+ TS2_I2C_ENUM_REMOTE_PRODUCT_TYPE_HI_V1},
+ .num_ids = 2, .ro = 1},
+ [85] = {.debug_name = "REMOTE_PRODUCTID_V2",
+ {{.name = "remote_productid_v2", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_u16_hex,
+ .id = { TS2_I2C_ENUM_REMOTE_PRODUCT_TYPE_LO,
+ TS2_I2C_ENUM_REMOTE_PRODUCT_TYPE_HI},
+ .num_ids = 2, .ro = 1},
+ [86] = {.debug_name = "REMOTE_SERNO_V1",
+ {{.name = "remote_serno_v1", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_serno_v1,
+ .id = { TS2_I2C_ENUM_REMOTE_SERNO_0, TS2_I2C_ENUM_REMOTE_SERNO_1,
+ TS2_I2C_ENUM_REMOTE_SERNO_2, TS2_I2C_ENUM_REMOTE_SERNO_3,
+ TS2_I2C_ENUM_REMOTE_SERNO_4, TS2_I2C_ENUM_REMOTE_SERNO_5},
+ .num_ids = 6, .ro = 1},
+ [87] = {.debug_name = "REMOTE_SERNO_V2",
+ {{.name = "remote_serno_v2", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_serno_v2,
+ .id = { TS2_I2C_ENUM_REMOTE_SERNO_0, TS2_I2C_ENUM_REMOTE_SERNO_1,
+ TS2_I2C_ENUM_REMOTE_SERNO_2, TS2_I2C_ENUM_REMOTE_SERNO_3,
+ TS2_I2C_ENUM_REMOTE_SERNO_4, TS2_I2C_ENUM_REMOTE_SERNO_5,
+ TS2_I2C_ENUM_REMOTE_SERNO_6, TS2_I2C_ENUM_REMOTE_SERNO_7},
+ .num_ids = 8, .ro = 1},
+ [88] = {.debug_name = "LOCAL_MFGRID",
+ {{.name = "local_mfgrid", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_u16_hex,
+ .id = { TS2_I2C_ENUM_MFGR_ID_LO, TS2_I2C_ENUM_MFGR_ID_HI},
+ .num_ids = 2, .ro = 1},
+ [89] = {.debug_name = "LOCAL_PRODUCTID",
+ {{.name = "local_productid", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_u16_hex,
+ .id = { TS2_I2C_ENUM_PRODUCT_TYPE_LO, TS2_I2C_ENUM_PRODUCT_TYPE_HI},
+ .num_ids = 2, .ro = 1},
+ [90] = {.debug_name = "LOCAL_SERNO",
+ {{.name = "local_serno", .mode = S_IRUGO},
+ .show = a6_generic_show, .store = NULL},
+ .format = format_serno_v2,
+ .id = { TS2_I2C_ENUM_SERNO_0, TS2_I2C_ENUM_SERNO_1,
+ TS2_I2C_ENUM_SERNO_2, TS2_I2C_ENUM_SERNO_3,
+ TS2_I2C_ENUM_SERNO_4, TS2_I2C_ENUM_SERNO_5,
+ TS2_I2C_ENUM_SERNO_6, TS2_I2C_ENUM_SERNO_7},
+ .num_ids = 8, .ro = 1},
+};
+
+
+struct device_attribute custom_devattr[] = {
+ {{.name = "a6_diag", .mode = S_IRUGO | S_IWUGO},
+ .show = a6_diag_show, .store = a6_diag_store},
+ {{.name = "validate_cksum", .mode = S_IRUGO},
+ .show = a6_val_cksum_show, .store = NULL},
+};
+
+static enum power_supply_property a6_fish_battery_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+};
+
+static enum power_supply_property a6_fish_power_properties[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static char *supply_list[] = {
+ "battery",
+};
+
+static int a6_fish_power_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val);
+
+static int a6_fish_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val);
+
+static struct power_supply a6_fish_power_supplies[] = {
+ {
+ .name = "battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = a6_fish_battery_properties,
+ .num_properties = ARRAY_SIZE(a6_fish_battery_properties),
+ .get_property = a6_fish_battery_get_property,
+ },
+ {
+ .name = "ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .supplied_to = supply_list,
+ .num_supplicants = ARRAY_SIZE(supply_list),
+ .properties = a6_fish_power_properties,
+ .num_properties = ARRAY_SIZE(a6_fish_power_properties),
+ .get_property = a6_fish_power_get_property,
+ },
+ {
+ .name = "usb",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .supplied_to = supply_list,
+ .num_supplicants = ARRAY_SIZE(supply_list),
+ .properties = a6_fish_power_properties,
+ .num_properties = ARRAY_SIZE(a6_fish_power_properties),
+ .get_property = a6_fish_power_get_property,
+ },
+#ifdef CONFIG_A6_ENABLE_DOCK_PS
+ {
+ .name = "dock",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .supplied_to = supply_list,
+ .num_supplicants = ARRAY_SIZE(supply_list),
+ .properties = a6_fish_power_properties,
+ .num_properties = ARRAY_SIZE(a6_fish_power_properties),
+ .get_property = a6_fish_power_get_property,
+ },
+#endif
+};
+
+#ifdef A6_PQ
+// a6 debugfs interface...
+#ifdef A6_DEBUG
+
+
+static int32_t a6_restart_aid_thread_fn(void* param)
+{
+ struct a6_device_state* state = param;
+ int32_t rc = 0;
+
+ daemonize("dbg_aidrst_%s", state->plat_data->dev_name);
+ while (!state->dbgflg_kill_raid) {
+ // stop aid task
+ rc = a6_stop_ai_dispatch_task(state);
+ if (rc) {
+ printk(KERN_ERR "%s: failed to stop ai_dispatch_task.\n", __func__);
+ break;
+ }
+
+ // re-start aid task
+ rc = a6_start_ai_dispatch_task(state);
+ if (rc) {
+ printk(KERN_ERR "%s: failed to start ai_dispatch_task.\n", __func__);
+ break;
+ }
+
+ // sleep
+ msleep(500);
+ }
+
+ rc = mutex_lock_interruptible(&state->dev_mutex);
+ if (rc) {
+ printk(KERN_ERR "%s: mutex_lock_interruptible interrupted\n", __func__);
+ return -ERESTARTSYS;
+ }
+ state->debug_restart_aid = 0;
+ state->dbgflg_kill_raid = 0;
+ mutex_unlock(&state->dev_mutex);
+
+ return rc;
+}
+
+static int a6_test_restart_aid_set(void *data, u64 val)
+{
+ int32_t rc = 0;
+ pid_t aiq_flush_pid = 0;
+ struct a6_device_state* state = data;
+ uint8_t in_val, curr_val;
+
+ in_val = val ? 1 : val;
+ rc = mutex_lock_interruptible(&state->dev_mutex);
+ if (rc) {
+ printk(KERN_ERR "%s: mutex_lock_interruptible interrupted\n", __func__);
+ return -ERESTARTSYS;
+ }
+ curr_val = state->debug_restart_aid;
+
+ // are we actually changing state?
+ if (in_val ^ curr_val) {
+ if (in_val) {
+ // prev task still being killed or active: fail
+ if (state->dbgflg_kill_raid || state->debug_restart_aid) {
+ printk(KERN_ERR "%s: prev task still being killed or active: fail\n",
+ __func__);
+ goto err0;
+ }
+ // create ai dispatcher task...
+ aiq_flush_pid = kernel_thread(a6_restart_aid_thread_fn, state, CLONE_KERNEL);
+ ASSERT(aiq_flush_pid >= 0);
+ state->debug_restart_aid = in_val;
+ }
+ else {
+ // prev task still being killed or not active: fail
+ if (state->dbgflg_kill_raid || !state->debug_restart_aid) {
+ printk(KERN_ERR "%s: prev task still being killed or not active: fail\n",
+ __func__);
+ goto err0;
+ }
+ // intent to kill task; task resets state as part of teardown
+ state->dbgflg_kill_raid = 1;
+ }
+ }
+ mutex_unlock(&state->dev_mutex);
+
+
+err0:
+ return rc;
+}
+static int a6_test_restart_aid_get(void *data, u64 *val)
+{
+ struct a6_device_state* state = data;
+
+ *val = state->debug_restart_aid;
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(fops_a6_test_restart_aid, a6_test_restart_aid_get, a6_test_restart_aid_set, "%llu\n");
+
+static int32_t a6_create_debug_interface(struct a6_device_state* state)
+{
+ int32_t rc = 0;
+ struct dentry *dentry_parent, *dentry_child;
+
+ dentry_parent = debugfs_create_dir("a6", 0);
+ if (IS_ERR(dentry_parent)) {
+ rc = PTR_ERR(dentry_parent);
+ goto err0;
+ }
+
+ dentry_child = debugfs_create_file("periodic_restart_aid", 0644,
+ dentry_parent, state, &fops_a6_test_restart_aid);
+ if (IS_ERR(dentry_child)) {
+ rc = PTR_ERR(dentry_child);
+ goto err0;
+ }
+
+ return 0;
+
+err0:
+ debugfs_remove_recursive(dentry_parent);
+ return rc;
+}
+#endif
+#endif // A6_PQ
+
+#ifdef A6_PQ
+// a6 action item types
+enum {
+ AI_I2C_TYPE,
+};
+
+struct a6_action_item {
+ void* ai_payload;
+ uint32_t ai_type: 4;
+ uint32_t ai_complete:1;
+ int32_t (*ai_do_action)(void* payload_param);
+ int32_t* (*ai_ret_code)(void* payload_param);
+ wait_queue_head_t ai_waitq;
+ struct list_head list;
+};
+
+/*
+ enq_a6_action_item: q's action item and blocks till action completion...
+*/
+int32_t enq_a6_action_item(struct a6_device_state* state, struct a6_action_item* ai, bool enq_tail)
+{
+ int32_t rc = 0;
+
+ mutex_lock(&state->aq_mutex);
+ if (true == enq_tail) {
+ list_add_tail(&ai->list, &state->aq_head);
+ }
+ else {
+ list_add(&ai->list, &state->aq_head);
+ }
+ mutex_unlock(&state->aq_mutex);
+
+ // signal the action_item dispatcher thread
+ complete(&state->aq_enq_complete);
+
+ // ** wait for action_item to be processed
+ a6_wait_event_ex(ai->ai_waitq, ai->ai_complete);
+ rc = *ai->ai_ret_code(ai->ai_payload);
+
+ return rc;
+}
+
+/*
+ dq_a6_action_item: dq's head item from action item list...
+*/
+struct a6_action_item* dq_a6_action_item(struct a6_device_state* state)
+{
+ struct a6_action_item* ai = NULL;
+
+ mutex_lock(&state->aq_mutex);
+ if (!list_empty(&state->aq_head)) {
+ ai = list_first_entry(&state->aq_head, struct a6_action_item, list);
+ list_del(state->aq_head.next);
+ }
+ mutex_unlock(&state->aq_mutex);
+
+ return ai;
+}
+
+/*
+ flush_a6_action_items: iterates action item list, dq's each item, marks it cancelled
+ and complete and then wakes up the requestor...
+*/
+int32_t flush_a6_action_items(struct a6_device_state* state)
+{
+ int32_t rc = 0;
+ struct a6_action_item* ai = NULL;
+
+ // lock list until all items removed
+ mutex_lock(&state->aq_mutex);
+ while (!list_empty(&state->aq_head)) {
+ // get first entry...
+ ai = list_first_entry(&state->aq_head, struct a6_action_item, list);
+ // ... and remove from list
+ list_del(state->aq_head.next);
+ *ai->ai_ret_code(ai->ai_payload) = -ECANCELED;
+ // set completion indicator
+ ai->ai_complete = 1;
+ // signal ai requestor
+ wake_up(&ai->ai_waitq);
+ }
+ mutex_unlock(&state->aq_mutex);
+
+ return rc;
+}
+
+
+struct ai_i2c_payload {
+ struct i2c_client* client;
+ struct i2c_msg* msg;
+ uint32_t num_msgs;
+ int32_t rc;
+};
+
+/*
+ do_i2c_action_item: i2c-specific action implementation...
+*/
+int32_t do_i2c_action_item(void* payload_param)
+{
+ struct ai_i2c_payload* trf_data = (struct ai_i2c_payload*)payload_param;
+
+ trf_data->rc = i2c_transfer(trf_data->client->adapter, trf_data->msg, trf_data->num_msgs);
+ udelay(700);
+ if (trf_data->rc < 0) {
+ printk(KERN_ERR "%s: err code: %d\n", __func__, trf_data->rc);
+ goto err0;
+ }
+
+ trf_data->rc = 0;
+err0:
+ return trf_data->rc;
+}
+
+/*
+ i2c_ret_code: i2c-specific ret-code reference retrieval...
+*/
+int32_t* i2c_ret_code(void* payload_param)
+{
+ struct ai_i2c_payload* trf_data = (struct ai_i2c_payload*)payload_param;
+
+ return (&trf_data->rc);
+}
+
+/*
+ q_a6_i2c_action_item: creates the action item structure and enq's it...
+*/
+int32_t q_a6_i2c_action_item(struct i2c_client* client, struct i2c_msg* msg, uint32_t num_msgs)
+{
+ int32_t ret = 0;
+ struct ai_i2c_payload data = {
+ .client = client,
+ .msg = msg,
+ .num_msgs = num_msgs,
+ .rc = 0
+ };
+
+ struct a6_action_item a6_i2c_ai = {
+ .ai_payload = &data,
+ .ai_type = AI_I2C_TYPE,
+ .ai_complete = 0,
+ .ai_do_action = do_i2c_action_item,
+ .ai_ret_code = i2c_ret_code,
+ .list = LIST_HEAD_INIT(a6_i2c_ai.list)
+ };
+
+ init_waitqueue_head(&a6_i2c_ai.ai_waitq);
+ ret = enq_a6_action_item(i2c_get_clientdata(client), &a6_i2c_ai, true/*enq tail*/);
+
+ return ret;
+}
+
+/*
+ ai_dispatch_thread_fn: function that handles ai processing in the aid task...
+*/
+int ai_dispatch_thread_fn(void* param)
+{
+ int32_t rc = 0;
+ struct a6_action_item* ai;
+ struct a6_device_state* state = param;
+
+ daemonize("aid_%s", state->plat_data->dev_name);
+ do {
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: about to wait for ai enq complete.\n", __func__);
+ // wait for ai to be enq'd
+ rc = wait_for_completion_interruptible(&state->aq_enq_complete);
+ if (rc >= 0) {
+ // check kill status: intent to kill?
+ if (test_bit(KILLING_AID_TASK, state->flags)) {
+ // remove all items from q
+ flush_a6_action_items(state);
+ // and signal killer before exiting stage...
+ complete(&state->aid_exit_complete);
+ break;
+ }
+
+ ai = dq_a6_action_item(state);
+ if (ai) {
+ int32_t ret_val;
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: got ai.\n", __func__);
+ // invoke ai-specific action
+ ret_val = ai->ai_do_action(ai->ai_payload);
+ if (ret_val) {
+ printk(KERN_ERR "%s: ai_do_action failed.\n", __func__);
+ }
+
+ // set completion indicator
+ ai->ai_complete = 1;
+ // signal ai requestor
+ wake_up(&ai->ai_waitq);
+ }
+ else {
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: no ai.\n", __func__);
+ }
+ }
+ // wait interrupted by -ERESTARTSYS: let's exit
+ else {
+ printk(KERN_ERR "%s: wait for action_item enq interrupted.\n",
+ __func__);
+ // force a panic...
+ BUG_ON(rc < 0);
+ }
+ } while(rc >= 0);
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "*** %s: exiting ai_dispatch_thread_fn.***\n", __func__);
+
+ return rc;
+}
+#endif // A6_PQ
+
+
+int32_t __a6_i2c_read_reg(struct i2c_client* client, const uint16_t* ids, uint32_t num_ids, uint8_t* out)
+{
+ int32_t ret = 0, i;
+ uint16_t swp_addr[num_ids];
+ struct i2c_msg msg[num_ids*2], *msg_itr;
+ struct a6_device_state* state = i2c_get_clientdata(client);
+#ifdef A6_I2C_PROFILE
+ ktime_t start, end;
+#endif
+
+#ifdef A6_PQ
+ if (test_bit(IS_QUIESCED, ((struct a6_device_state*)i2c_get_clientdata(client))->flags)) {
+ ret = -ECANCELED;
+ goto err0;
+ }
+#endif // A6_PQ
+
+ // force A6 wakeup...
+ if (start_last_a6_activity) {
+ long diff_time = (long)jiffies - (long)start_last_a6_activity;
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: time since last activity: %ld ms\n",
+ __func__, diff_time * 1000/HZ);
+ }
+ start_last_a6_activity = jiffies;
+
+ // prevent timer expiry during force_wake related action...
+ del_timer(&state->a6_force_wake_timer);
+
+ mutex_lock(&state->a6_force_wake_mutex);
+ // a6 external wake enabled?
+ if (test_bit(CAP_PERIODIC_WAKE, state->flags)) {
+ if (!test_bit(FORCE_WAKE_ACTIVE_BIT, state->flags)) {
+ struct a6_wake_ops* wake_ops = (struct a6_wake_ops*)state->plat_data->wake_ops;
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR,
+ "%s: disabling periodic_wake and switching to force_wake\n",
+ __func__);
+
+ // periodic wake active: disable it and switch to force wake
+ if (wake_ops->disable_periodic_wake) {
+ wake_ops->disable_periodic_wake(wake_ops->data);
+ }
+ if (wake_ops->force_wake) {
+ wake_ops->force_wake(wake_ops->data);
+ }
+
+ set_bit(FORCE_WAKE_ACTIVE_BIT, state->flags);
+ }
+ }
+ mutex_unlock(&state->a6_force_wake_mutex);
+
+#ifdef A6_I2C_PROFILE
+ start = ktime_get();
+#endif
+ msg_itr = &msg[0];
+ for (i = num_ids-1; i >= 0; i--) {
+ msg_itr->addr = client->addr;
+ msg_itr->flags = 0;
+ msg_itr->len = sizeof(uint16_t);
+ swp_addr[i] = ids[i] >> 8 | ids[i] << 8;
+ msg_itr->buf = (uint8_t*)&swp_addr[i];
+
+ (msg_itr+1)->addr = client->addr;
+ (msg_itr+1)->flags = I2C_M_RD;
+ (msg_itr+1)->len = sizeof(uint8_t);
+ (msg_itr+1)->buf = &out[i];
+#ifdef CONFIG_A6_I2C_SINGLE_BYTE
+#ifdef A6_PQ
+ ret = q_a6_i2c_action_item(client, msg, 2);
+#else
+ ret = i2c_transfer(client->adapter, msg, 2);
+#endif // A6_PQ
+#endif
+ msg_itr += 2;
+ }
+
+
+#ifndef CONFIG_A6_I2C_SINGLE_BYTE
+#ifdef A6_PQ
+ ret = q_a6_i2c_action_item(client, msg, num_ids*2);
+#else
+ ret = i2c_transfer(client->adapter, msg, num_ids*2);
+#endif // A6_PQ
+#endif
+ //msleep(1);
+#ifdef A6_I2C_PROFILE
+ {
+ end = ktime_get();
+ printk(KERN_ERR "%s[0x%02x]: elpased time: %lld\n",
+ __func__, client->addr, ktime_to_ns(ktime_sub(end, start)));
+ }
+#endif
+ if (ret < 0) {
+ printk(KERN_ERR "%s[0x%02x]: err code: %d\n", __func__, client->addr, ret);
+ // reset the force_wake timer inedependent of i2c failure
+ //goto err0;
+ }
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "ret val: 0x%x\n", *out);
+
+ if (test_bit(CAP_PERIODIC_WAKE, state->flags)) {
+ // [re]start force-wake expiry timer
+ mod_timer(&state->a6_force_wake_timer, jiffies+FORCE_WAKE_TIMER_EXPIRY);
+ }
+
+err0:
+ return ret;
+}
+
+int32_t a6_i2c_read_reg(struct i2c_client* client, const uint16_t* ids, uint32_t num_ids, uint8_t* out)
+{
+ int32_t ret;
+#ifdef A6_I2C_RETRY
+ int32_t retry = 5;
+#else
+ int32_t retry = 0;
+#endif
+
+ do {
+ ret = __a6_i2c_read_reg(client, ids, num_ids, out);
+ if (ret < 0) {
+ printk("%s: a6 i2c transaction failed. %s...\n", __func__, retry ? "retry" : " ");
+ msleep(30);
+ }
+ } while (0 != ret && retry-- > 0);
+
+ return ret;
+}
+
+int32_t __a6_i2c_write_reg(struct i2c_client* client, const uint16_t* ids, uint32_t num_ids, const uint8_t* in)
+{
+ int32_t ret = 0, i;
+ uint8_t i2c_buf[(2+1)*num_ids];
+ struct i2c_msg msg[num_ids], *msg_itr;
+ struct a6_device_state* state = i2c_get_clientdata(client);
+
+#ifdef A6_PQ
+ if (test_bit(IS_QUIESCED, ((struct a6_device_state*)i2c_get_clientdata(client))->flags)) {
+ ret = -ECANCELED;
+ goto err0;
+ }
+#endif // A6_PQ
+
+ // force A6 wakeup...
+ if (start_last_a6_activity) {
+ long diff_time = (long)jiffies - (long)start_last_a6_activity;
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: time since last activity: %ld ms\n",
+ __func__, diff_time * 1000/HZ);
+ }
+ start_last_a6_activity = jiffies;
+
+ // prevent timer expiry during force_wake related action...
+ del_timer(&state->a6_force_wake_timer);
+
+ mutex_lock(&state->a6_force_wake_mutex);
+ if (test_bit(CAP_PERIODIC_WAKE, state->flags)) {
+ if (!test_and_set_bit(FORCE_WAKE_ACTIVE_BIT, state->flags)) {
+ struct a6_wake_ops* wake_ops = (struct a6_wake_ops*)state->plat_data->wake_ops;
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR,
+ "%s: disabling periodic_wake and switching to force_wake\n",
+ __func__);
+
+ // periodic wake active: disable it and switch to force wake
+ if (wake_ops->disable_periodic_wake) {
+ wake_ops->disable_periodic_wake(wake_ops->data);
+ }
+ if (wake_ops->force_wake) {
+ wake_ops->force_wake(wake_ops->data);
+ }
+
+ set_bit(FORCE_WAKE_ACTIVE_BIT, state->flags);
+ }
+ }
+ mutex_unlock(&state->a6_force_wake_mutex);
+
+ msg_itr = &msg[0];
+ for (i = num_ids-1; i >= 0; i--) {
+ i2c_buf[(i*(2+1))+0] = (uint8_t)(ids[i] >> 8);
+ i2c_buf[(i*(2+1))+1] = (uint8_t)(ids[i]);
+ i2c_buf[(i*(2+1))+2] = in[i];
+
+ msg_itr->addr = client->addr;
+ msg_itr->flags = 0;
+ msg_itr->len = 2+1;
+ msg_itr->buf = &i2c_buf[i*(2+1)];
+#ifdef CONFIG_A6_I2C_SINGLE_BYTE_WRITE
+#ifdef A6_PQ
+ ret = q_a6_i2c_action_item(client, msg_itr, 1);
+#else
+ ret = i2c_transfer(client->adapter, msg_itr, 1);
+#endif // A6_PQ
+ udelay(700);
+ if (ret < 0)
+ break;
+#endif
+ msg_itr++;
+ }
+
+#ifndef CONFIG_A6_I2C_SINGLE_BYTE_WRITE
+#ifdef A6_PQ
+ ret = q_a6_i2c_action_item(client, msg, num_ids);
+#else
+ ret = i2c_transfer(client->adapter, msg, num_ids);
+#endif // A6_PQ
+ //msleep(1);
+#endif
+ if (ret < 0) {
+ printk(KERN_ERR "%s[0x%02x]: err code: %d\n", __func__, client->addr, ret);
+ // reset the force_wake timer inedependent of i2c failure
+ //goto err0;
+ }
+
+ if (test_bit(CAP_PERIODIC_WAKE, state->flags)) {
+ // [re]start force-wake expiry timer
+ mod_timer(&state->a6_force_wake_timer, jiffies+FORCE_WAKE_TIMER_EXPIRY);
+ }
+
+err0:
+ return ret;
+}
+
+int32_t a6_i2c_write_reg(struct i2c_client* client, const uint16_t* ids, uint32_t num_ids, const uint8_t* in)
+{
+ int32_t ret;
+#ifdef A6_I2C_RETRY
+ int32_t retry = 5;
+#else
+ int32_t retry = 0;
+#endif
+
+ do {
+ ret = __a6_i2c_write_reg(client, ids, num_ids, in);
+ if (ret < 0) {
+ printk("%s: a6 i2c transaction failed. %s...\n", __func__, retry ? "retry" : " ");
+ msleep(30);
+ }
+ } while (0 != ret && retry-- > 0);
+
+ return ret;
+}
+
+
+uint8_t _convert_hex_char_to_decimal(uint8_t x)
+{
+ x -= '0';
+ if (x > 9) {
+ x = x - ('A' - ('9' + 1));
+ if (x > 15) {
+ x = x - ('a' - 'A');
+ }
+ }
+
+ return x;
+}
+
+int32_t a6_init_state(struct i2c_client *client)
+{
+ int32_t ret = 0;
+ struct a6_device_state* state = i2c_get_clientdata(client);
+ struct a6_register_desc* reg_desc;
+ uint8_t vals[id_size];
+
+ // early initialization of cached rsense to prevent un-initialized usage
+ // due to early-boot i2c failures.
+ state->cached_rsense_val = RSENSE_DEFAULT;
+
+ /* (1) enable external/internal wake, if required */
+ /* periodic wake capability enabled? */
+ if (test_bit(CAP_PERIODIC_WAKE, state->flags)) {
+ struct a6_wake_ops* wake_ops = (struct a6_wake_ops*)state->plat_data->wake_ops;
+
+ // initialize timer used to force sleep after a force wake...
+ setup_timer(&state->a6_force_wake_timer, a6_force_wake_timer_callback, (ulong)state);
+
+ /* enable external periodic wake? */
+ if (wake_ops->enable_periodic_wake) {
+ printk(KERN_ERR "%s: enabling external PMIC-generated A6 wake.\n", __func__);
+
+ wake_ops->enable_periodic_wake(wake_ops->data);
+
+ /* disable a6 internal-wake... */
+ reg_desc = &a6_register_desc_arr[34];
+ /* format wakeup parameters in register format */
+ vals[0] = 0;
+ ret = a6_i2c_write_reg(client, reg_desc->id, reg_desc->num_ids, vals);
+ if (ret < 0) {
+ goto err0;
+ }
+ }
+ /* enable internal periodic wake? */
+ else if (wake_ops->internal_wake_enable_state) {
+ uint32_t wake_period = 0, wake_enable = 1;
+
+ printk(KERN_ERR "%s: enabling A6 internal wake.\n", __func__);
+ /* default wake_period, if required */
+ if (!wake_ops->internal_wake_period) {
+ wake_period = 0x0;
+ }
+ else {
+ wake_period = wake_ops->internal_wake_period(wake_ops->data);
+ if (wake_period > 0x100) {
+ wake_period = 0x100;
+ }
+ }
+
+ reg_desc = &a6_register_desc_arr[34];
+
+ if (!wake_period) {
+ wake_enable = 0x0;
+ }
+
+ // bit [4]: Enable sleep.
+ // bit [3]: Enable automatic wakeup.
+ // bits [2:0]: Sleep period.
+ if (wake_period) {
+ wake_period = find_last_bit((const unsigned long*)&wake_period, 32);
+ wake_period--;
+ }
+ vals[0] = 0x10 | wake_enable << 3 | (wake_period & 0x07);
+ printk(KERN_ERR "%s: TS2_I2C_WAKEUP_PERIOD = 0x%02x\n",
+ __func__, vals[0]);
+ ret = a6_i2c_write_reg(client, reg_desc->id, reg_desc->num_ids, vals);
+ if (ret < 0) {
+ goto err0;
+ }
+ }
+ else {
+ BUG();
+ }
+ }
+ /* periodic wake capability not defined: force a6 awake using SBW_WAKEUP */
+ else {
+ printk(KERN_ERR "%s: permanently forcing A6 awake.\n", __func__);
+ gpio_set_value(state->plat_data->sbw_wkup_gpio, 1);
+ }
+
+ /* (2) cache rsense val */
+ reg_desc = &a6_register_desc_arr[19];
+
+ memset(vals, 0, sizeof(vals));
+ ret = a6_i2c_read_reg(client, reg_desc->id, reg_desc->num_ids, vals);
+ if (ret < 0) {
+ //goto err0;
+ }
+
+ /* rsense == 0: invalid and results in default
+ (logic compatibile with legacy w1 driver implementation) */
+ if (vals[0]) {
+ state->cached_rsense_val = 1000/vals[0];
+ if (!state->cached_rsense_val) {
+ state->cached_rsense_val = RSENSE_DEFAULT;
+ }
+ }
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR,
+ "%s: (cached) rsense value: %u\n", __func__, state->cached_rsense_val);
+
+ /* (3) enable irqs: MASK_A2A_CONNECT_CHANGE, MASK_FLAGS_CHANGE */
+ reg_desc = &a6_register_desc_arr[3];
+
+ vals[0] = 0xf3;
+ ret = a6_i2c_write_reg(client, reg_desc->id, reg_desc->num_ids, vals);
+ if (ret < 0) {
+ goto err0;
+ }
+
+ /* (4) enable irqs:
+ MASK_BAT_TEMP_HIGH, MASK_BAT_TEMP_LOW,
+ MASK_BAT_VOLT_LOW, MASK_BAT_RARC_CRIT,
+ MASK_BAT_RARC_LOW2, MASK_BAT_RARC_LOW1
+ */
+ reg_desc = &a6_register_desc_arr[2];
+
+ vals[0] = 0xc0;
+ ret = a6_i2c_write_reg(client, reg_desc->id, reg_desc->num_ids, vals);
+ if (ret < 0) {
+ goto err0;
+ }
+ /* read version */
+ reg_desc = &a6_register_desc_arr[32];
+
+ memset(vals, 0, sizeof(vals));
+ ret = a6_i2c_read_reg(client, reg_desc->id, reg_desc->num_ids, vals);
+ if (ret < 0) {
+ printk(KERN_ERR "%s: error reading A6 version\n", __func__);
+ }
+ else {
+ uint8_t buf[150];
+
+ reg_desc->format(state, vals, buf, sizeof(buf));
+ printk(KERN_INFO "%s", buf);
+ }
+
+ // if first init (device boot): clear all interrupt status regs because we might
+ // have missed the level-triggered a6-irq during device boot and the user-space
+ // components read a6 registers to set their initial state correctly...
+ if (!test_bit(IS_INITIALIZED_BIT, state->flags)) {
+ vals[0] = 0xff;
+
+ /* clear int_status3 */
+ reg_desc = &a6_register_desc_arr[7];
+ ret = a6_i2c_write_reg(state->i2c_dev, reg_desc->id, reg_desc->num_ids, vals);
+ if (ret < 0) {
+ printk(KERN_ERR "%s: error writing reg: %s, id: 0x%x\n",
+ __func__, reg_desc->debug_name, reg_desc->id[0]);
+ goto err0;
+ }
+
+ /* clear int_status2 */
+ reg_desc = &a6_register_desc_arr[6];
+ ret = a6_i2c_write_reg(state->i2c_dev, reg_desc->id, reg_desc->num_ids, vals);
+ if (ret < 0) {
+ printk(KERN_ERR "%s: error writing reg: %s, id: 0x%x\n",
+ __func__, reg_desc->debug_name, reg_desc->id[0]);
+ goto err0;
+ }
+
+ /* clear int_status1 */
+ reg_desc = &a6_register_desc_arr[5];
+ ret = a6_i2c_write_reg(state->i2c_dev, reg_desc->id, reg_desc->num_ids, vals);
+ if (ret < 0) {
+ printk(KERN_ERR "%s: error writing reg: %s, id: 0x%x\n",
+ __func__, reg_desc->debug_name, reg_desc->id[0]);
+ goto err0;
+ }
+ }
+
+ set_bit(IS_INITIALIZED_BIT, state->flags);
+
+err0:
+ return ret;
+}
+
+int32_t format_current(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer)
+{
+ int32_t ret;
+ int32_t conv_val = *(int16_t*)val; // (val[0]<<8) + val[1];
+
+ /* in uA */
+ /* TODO: Bootie source code states:
+ * Define R sense as 20 mOhms, as opposed as a variable and reading it
+ * from the pack, not all packs have the correct R sense programmed
+ */
+ conv_val = (conv_val * 3125)/2/(int32_t)state->cached_rsense_val;
+
+ ret = scnprintf(fmt_buffer, size_buffer, "%d\n", conv_val);
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR,
+ "Current value (uA): %s\n", fmt_buffer);
+ return ret;
+}
+
+int32_t format_voltage(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer)
+{
+ int32_t ret;
+ int32_t conv_val = *(int16_t*)val; // (val[0]<<8) + val[1];
+
+ /* in uV -- 11-bit signed value, unit = 4880uV */
+ conv_val = (conv_val>>5) * 4880;
+ ret = scnprintf(fmt_buffer, size_buffer, "%d\n", conv_val);
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_INFO, "Voltage value (uV): %s\n",
+ fmt_buffer);
+ return ret;
+}
+
+int32_t r_format_voltage(const struct a6_device_state* state, const uint8_t* fmt_buffer,
+ uint8_t* val, uint32_t size_buffer)
+{
+ int32_t ret;
+ uint32_t conv_val;
+ const char *bufp;
+ char *endp = NULL;
+
+ bufp = fmt_buffer;
+ // let strtoul perform base determination (handles '0x' and '0' prefixes) ...
+ conv_val = simple_strtoul(bufp, &endp, 0);
+ /* in uV -- 11-bit signed value, unit = 4880uV */
+ conv_val = (conv_val / 4880) << 5;
+ // capped at 16 bits...
+ if (conv_val >> 0x10) {
+ ret = 0;
+ goto err0;
+ }
+ ret = (uint32_t)endp - (uint32_t)bufp;
+
+ *(uint16_t*)val = conv_val;
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: ip: %s op: %d\n",
+ __func__, fmt_buffer, conv_val);
+
+err0:
+ return ret;
+}
+/**
+ * Take the Accumulated Current Register (ACR), which is expressed in units of 6.25uVh
+ * and convert to uAh.
+ */
+int32_t format_rawcoulomb(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer)
+{
+ int32_t ret;
+ int32_t conv_val = *(int16_t*)val; // (val[0]<<8) + val[1];
+
+ // in uAh
+ conv_val = (conv_val * 6250) / (int32_t)state->cached_rsense_val;
+ ret = scnprintf(fmt_buffer, size_buffer, "%d.%03d\n", conv_val/1000,
+ ((conv_val >= 0)? conv_val : -conv_val) % 1000);
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_INFO, "Raw Coulomb value (uAh): %s\n",
+ fmt_buffer);
+ return ret;
+}
+
+/**
+ * Take the Remaining Active Absolute Capacity (RAAC) register, which is in
+ * units of 1.6 mAh and convert to mAh
+ */
+int32_t format_coulomb(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer)
+{
+ int32_t ret;
+ int32_t conv_val = *(int16_t*)val;
+
+ // in mAh
+ conv_val = (conv_val * 8) / 5;
+ ret = scnprintf(fmt_buffer, size_buffer, "%d\n", conv_val);
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_INFO, "Coulomb value (mAh): %s\n",
+ fmt_buffer);
+ return ret;
+}
+
+int32_t format_age(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer)
+{
+ int32_t ret, conv_val;
+
+ // Age conversion factor, in .01 percent. Raw value 0x80 = 100%
+ conv_val = (10000*val[0]) >> 7;
+ ret = scnprintf(fmt_buffer, size_buffer, "%d.%02d\n",
+ conv_val/100, conv_val%100);
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_INFO, "Battery life left (%%): %s\n",
+ fmt_buffer);
+ return ret;
+}
+
+int32_t format_fullx(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer)
+{
+ int32_t ret;
+ int32_t conv_val = *(int16_t*)val; // (val[0]<<8) + val[1];
+
+ // Convert 6.25uVh to uAh
+ conv_val = (conv_val * 6250) / state->cached_rsense_val;
+ ret = scnprintf(fmt_buffer, size_buffer,
+ "%d.%03d\n", conv_val/1000, conv_val%1000);
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_INFO, "Battery life left (uAh): %s\n",
+ fmt_buffer);
+ return ret;
+}
+
+int32_t format_temp(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer)
+{
+ int32_t ret;
+ int conv_val = val[1]; /* Ignoring the fraction part */
+
+ // in C
+ ret = snprintf(fmt_buffer, size_buffer, "%d\n", (int8_t)conv_val);
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_INFO, "Temperature (C): %s\n",
+ fmt_buffer);
+ return ret;
+}
+
+int32_t format_command(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer)
+{
+ int32_t ret;
+ int conv_command_debug_code = val[0];
+
+ ret = snprintf(fmt_buffer, size_buffer, "%d\n", (int8_t)conv_command_debug_code);
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_INFO, "debug code(C): %s\n",
+ fmt_buffer);
+ return ret;
+}
+
+int32_t format_accessory(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer)
+{
+ int32_t ret;
+
+ ret = scnprintf(fmt_buffer, size_buffer, "%02X\n", val[0]);
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_INFO, "%s", fmt_buffer);
+ return ret;
+}
+
+int32_t format_comm_status(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer)
+{
+ int32_t ret;
+
+ ret = scnprintf(fmt_buffer, size_buffer, "%02X\n", val[0]);
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_INFO, "%s", fmt_buffer);
+ return ret;
+}
+
+int32_t r_format_temp(const struct a6_device_state* state, const uint8_t* fmt_buffer,
+ uint8_t* val, uint32_t size_buffer)
+{
+ int32_t ret;
+ int32_t conv_val;
+ const char *bufp;
+ char *endp = NULL;
+
+ bufp = fmt_buffer;
+ // let strtoul perform base determination (handles '0x' and '0' prefixes) ...
+ conv_val = simple_strtol(bufp, &endp, 0);
+ printk(KERN_ERR "conv_val : %d, hex: %x\n", conv_val, conv_val);
+ // capped at 8 bits...
+ if (conv_val < -128) {
+ ret = 0;
+ goto err0;
+ }
+ ret = (uint32_t)endp - (uint32_t)bufp;
+
+ /* Ignoring the fraction part */
+ *(uint16_t*)val = (uint8_t)conv_val << 8;
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: ip: %s op: %d\n",
+ __func__, fmt_buffer, conv_val<<8);
+
+err0:
+ return ret;
+}
+
+int32_t format_status(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer)
+{
+ uint32_t conv_val = *val;
+ int32_t count, i;
+
+ const char* status_bits[] = {
+ NULL,
+ "power-on-reset",
+ "undervoltage",
+ NULL,
+ "learn",
+ "standby-empty",
+ "active-empty",
+ "charge-termination"};
+
+ for (i = count = 0; i < sizeof(status_bits)/sizeof(status_bits[0]); i++) {
+ if ((conv_val & (1 << i)) && status_bits[i] != NULL) {
+ count += scnprintf(fmt_buffer + count, size_buffer-count, "%s\n", status_bits[i]);
+ }
+ }
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR,
+ "Battery status: %s\n", fmt_buffer);
+
+ return count;
+}
+
+int32_t format_rsense(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer)
+{
+ int32_t ret;
+ uint32_t conv_val = *val;
+
+ ret = scnprintf(fmt_buffer, size_buffer, "%u\n", (!conv_val) ? conv_val : 1000/conv_val);
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_INFO, "rsense (ohm): %s\n",
+ fmt_buffer);
+ return ret;
+}
+
+/**
+ * Determine what, if any chargers are attached to the system. Only one
+ * result will be returned, even if multiple chargers are detected.
+ *
+ * Output is a combination of:
+ * 0x01 - Puck Detected
+ * 0x02 - Puck Connected
+ * 0x04 - Wired Power Detected
+ * 0x08 - Puck Power Connected
+ * 0x10 - Wired Power Connected
+ * 0x20 - Battery Present
+ *
+ * Not all values are currently supported.
+ */
+int32_t format_charge_source_status(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer)
+{
+ struct charge_source_status_map {
+ uint32_t cs_mask;
+ const char* cs_name;
+ } cs_map[] = {
+ [0] = {0x01, "Puck Detected"},
+ [1] = {0x02, "Puck Connected"},
+ [2] = {0x04, "Wired Power Detected"},
+ [3] = {0x08, "Puck Power Connected"},
+ [4] = {0x10, "Wired Power Connected"},
+ [5] = {0x20, "Battery Present"},
+ };
+ uint32_t conv_val = *val, count = 0;
+ int32_t i;
+
+ for(i = 0; i < sizeof(cs_map)/sizeof(cs_map[0]); i++) {
+ if (conv_val & cs_map[i].cs_mask) {
+ count += scnprintf(fmt_buffer + count, (size_buffer - 1) - count, "%s\n", cs_map[i].cs_name);
+ }
+ }
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR,
+ "charge source status (formatted): %s\n", fmt_buffer);
+
+#ifdef A6_REPORT_CONNECTED_ONLY
+ conv_val = 0;
+ /* map charge source status to user-space values */
+ /* TODO: differentiate wall charger from USB */
+ if (val[0] & TS2_I2C_FLAGS_2_WIRED_CHARGE) conv_val |= 1;
+ if (val[0] & TS2_I2C_FLAGS_2_PUCK_CHARGE) conv_val |= 4;
+#endif
+
+ /* output register contents */
+ count = scnprintf(fmt_buffer, size_buffer, "%u\n", conv_val);
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR,
+ "charge source status (literal): %s\n", fmt_buffer);
+ return count;
+}
+
+int32_t format_version(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer)
+{
+ int32_t ret;
+
+ ret = scnprintf(fmt_buffer, size_buffer,
+ "A6 Version: HW: %d, FW (M.m.B): %d.%d.%d, ManID: %d, ProdTyp: %d\n",
+ val[12], val[15], val[14], val[13],
+ *(int16_t*)&(val[0]), *(int16_t*)&(val[2]));
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_INFO, "%s", fmt_buffer);
+ return ret;
+}
+
+int32_t format_v_offset(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer)
+{
+ int32_t ret;
+ uint32_t conv_val = *val;
+
+ ret = scnprintf(fmt_buffer, size_buffer, "%u\n", conv_val);
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_INFO, "v_offset: %s\n", fmt_buffer);
+ return ret;
+}
+
+int32_t format_raw_unsigned(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer)
+{
+ int32_t ret;
+ uint32_t conv_val = *val;
+
+ ret = scnprintf(fmt_buffer, size_buffer, "%u\n", conv_val);
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_INFO, "raw_unsigned: %s\n", fmt_buffer);
+ return ret;
+}
+
+int32_t format_u16_hex(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer)
+{
+ int32_t ret;
+ uint16_t conv_val = *(uint16_t*)val;
+
+ ret = scnprintf(fmt_buffer, size_buffer, "0x%04hx\n", conv_val);
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_INFO, "u16_hex: %s\n", fmt_buffer);
+ return ret;
+}
+
+int32_t format_max_power_available(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer)
+{
+ int32_t ret;
+ uint32_t vnode_max_val = *(uint16_t*)val;
+ uint32_t inode_max_val = *(uint16_t*)(val+2);
+ uint32_t power_val = *(val+4);
+ uint32_t conv_val;
+
+ conv_val = (vnode_max_val * inode_max_val * power_val)/2560;
+ ret = scnprintf(fmt_buffer, size_buffer, "%u\n", conv_val);
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_INFO, "%s", fmt_buffer);
+ return ret;
+}
+
+int32_t format_accessory_combo(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer)
+{
+ int32_t ret;
+
+ ret = scnprintf(fmt_buffer, size_buffer,
+ "0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x "
+ "0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x \n",
+ val[0], val[1], val[2], val[3], val[4], val[5], val[6], val[7],
+ val[8], val[9], val[10], val[11], val[12], val[13], val[14], val[15]);
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_INFO, "%s", fmt_buffer);
+ return ret;
+}
+
+int32_t format_serno_v1(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer)
+{
+ int32_t ret;
+
+ ret = scnprintf(fmt_buffer, size_buffer, "%02x%02x%02x%02x%02x%02x\n",
+ val[0], val[1], val[2], val[3], val[4], val[5]);
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_INFO, "%s", fmt_buffer);
+ return ret;
+}
+
+int32_t format_serno_v2(const struct a6_device_state* state, const uint8_t* val,
+ uint8_t* fmt_buffer, uint32_t size_buffer)
+{
+ int32_t ret;
+
+ ret = scnprintf(fmt_buffer, size_buffer, "%02x%02x%02x%02x%02x%02x%02x%02x\n",
+ val[0], val[1], val[2], val[3], val[4], val[5], val[6], val[7]);
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_INFO, "%s", fmt_buffer);
+ return ret;
+}
+
+static ssize_t a6_get_reg_vals(struct device *dev, struct device_attribute *attr, uint8_t *vals, unsigned num_vals)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ int32_t ret = 0;
+ struct a6_register_desc* reg_desc;
+ struct a6_device_state* state = i2c_get_clientdata(client);
+
+ // critsec for manipulating flags
+ ret = mutex_lock_interruptible(&state->dev_mutex);
+ if (ret) {
+ printk(KERN_ERR "%s: mutex_lock_interruptible interrupted(1)\n", __func__);
+ return -ERESTARTSYS;
+ }
+
+ // are we busy?
+ while (test_and_set_bit(DEVICE_BUSY_BIT, state->flags)) {
+ // yes: are we in bootload phase?
+ if (!test_bit(BOOTLOAD_ACTIVE_BIT, state->flags)) {
+ // no: so go ahead and allow concurrent i2c ops (these are
+ // synchronized separately to allow priority based execution).
+ break;
+ }
+
+ // in bootload phase: get on a waitq
+ mutex_unlock(&state->dev_mutex);
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: about to wait for device non-busy...\n", __func__);
+
+ // bootload bit set? wait to be cleared (at least 5 mins: in jiffies)
+ ret = wait_event_interruptible_timeout(state->dev_busyq,
+ !test_bit(BOOTLOAD_ACTIVE_BIT, state->flags), 300*HZ);
+ if (!ret) {
+ printk(KERN_ERR "%s: wait on device busy timed-out/interrupted\n", __func__);
+ // reset busy state
+ clear_bit(DEVICE_BUSY_BIT, state->flags);
+ // and continue...
+ }
+
+ // we're about to manipulate flags again: acquire critsec
+ ret = mutex_lock_interruptible(&state->dev_mutex);
+ if (ret) {
+ printk(KERN_ERR "%s: mutex_lock_interruptible interrupted(2)\n", __func__);
+ return -ERESTARTSYS;
+ }
+ }
+
+ // increment busy refcount
+ state->busy_count++;
+ // done with flags: exit critsec
+ mutex_unlock(&state->dev_mutex);
+
+ reg_desc = container_of(attr, struct a6_register_desc, dev_attr);
+
+ memset(vals, 0, sizeof(vals[0])*num_vals);
+ ret = a6_i2c_read_reg(client, reg_desc->id, reg_desc->num_ids, vals);
+
+ // reset busy state
+ mutex_lock(&state->dev_mutex);
+ // decrement busy refcount
+ if (state->busy_count) {
+ state->busy_count--;
+ }
+ if (!state->busy_count) {
+ clear_bit(DEVICE_BUSY_BIT, state->flags);
+ }
+ mutex_unlock(&state->dev_mutex);
+ wake_up_interruptible(&state->dev_busyq);
+
+ return ret;
+}
+
+static ssize_t a6_generic_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ int32_t ret = 0, i = 0;
+ uint8_t vals[id_size];
+ struct a6_register_desc* reg_desc;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct a6_device_state* state = i2c_get_clientdata(client);
+
+ ret = a6_get_reg_vals(dev, attr, vals, id_size);
+
+ if (ret < 0) return ret;
+
+ reg_desc = container_of(attr, struct a6_register_desc, dev_attr);
+
+ if (reg_desc->format) {
+ ret = reg_desc->format(state, vals, buf, PAGE_SIZE);
+ }
+ else {
+ /* if output restricted to 2 8-bit values, show as int16_t
+ (common case of msb+lsb retrieval) */
+ if (2 == reg_desc->num_ids) {
+ ret = scnprintf(buf, PAGE_SIZE, "%d\n", *(int16_t*)vals);
+ }
+ /* if output > or < 2 8-bit values, show as
+ * space-delimited list of int8_t values
+ */
+ else {
+ i = ret = 0;
+ while(i < reg_desc->num_ids) {
+ ret += scnprintf(buf+ret, PAGE_SIZE-ret, "%d ", (int8_t)vals[i]);
+ i++;
+ }
+ ret += scnprintf(buf+ret, PAGE_SIZE-ret, "\n");
+ }
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "Buffer: %s\n", buf);
+ }
+
+#ifdef A6_DEBUG
+ {
+ uint8_t d_ids[reg_desc->num_ids * (4+2+1) + 1];
+ uint8_t d_vals[reg_desc->num_ids * (2+2+1) + 1];
+ int32_t i = 0, ret_ids = 0, ret_vals = 0;
+
+ while (i < reg_desc->num_ids) {
+ ret_ids += sprintf(d_ids+ret_ids, "0x%04x ", reg_desc->id[i]);
+ ret_vals += sprintf(d_vals+ret_vals, "0x%02x ", vals[i]);
+ i++;
+ }
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_INFO,
+ "showing reg name: %s, num_ids: %d, ids: %s, vals: %s\n",
+ reg_desc->debug_name, reg_desc->num_ids, d_ids, d_vals);
+ }
+#endif
+
+ return ret;
+}
+
+static ssize_t a6_reg_get(struct device *dev, unsigned regnum, void *buf)
+{
+ int32_t ret = 0;
+ uint8_t vals[id_size];
+ struct i2c_client *client = to_i2c_client(dev);
+ struct a6_device_state* state = i2c_get_clientdata(client);
+ struct device_attribute *attr = &a6_register_desc_arr[regnum].dev_attr;
+ int32_t rsense = (int32_t)state->cached_rsense_val;
+
+ ret = a6_get_reg_vals(dev, attr, vals, id_size);
+
+ if (ret < 0) {
+ printk(KERN_ERR "%s: a6_get_reg_vals failed for regnum %u\n",
+ __func__, regnum);
+ return ret;
+ }
+
+ ret = 0;
+
+ switch (regnum) {
+ case A6_REG_TS2_I2C_FLAGS_2:
+ {
+ uint32_t *out_val = (uint32_t*)buf;
+ *out_val = *vals;
+ }
+ break;
+ case A6_REG_TS2_I2C_BAT_RARC:
+ {
+ int32_t *out_val = (int32_t*)buf;
+ *out_val = *vals;
+ }
+ break;
+ case A6_REG_TS2_I2C_BAT_AVG_CUR_LSB_MSB:
+ case A6_REG_TS2_I2C_BAT_CUR_LSB_MSB:
+ {
+ int32_t *out_val = (int32_t*)buf;
+ *out_val = *(int16_t*)vals;
+ *out_val = (*out_val * 3125) / 2 / rsense;
+ }
+ break;
+ case A6_REG_TS2_I2C_BAT_VOLT_LSB_MSB:
+ {
+ int32_t *out_val = (int32_t*)buf;
+ *out_val = *(int16_t*)vals;
+ *out_val = (*out_val >> 5) * 4800;
+ }
+ break;
+ case A6_REG_TS2_I2C_BAT_FULL40_LSB_MSB:
+ case A6_REG_TS2_I2C_BAT_COULOMB_LSB_MSB:
+ {
+ int32_t *out_val = (int32_t*)buf;
+ *out_val = *(int16_t*)vals;
+ *out_val = (*out_val * 6250) / rsense;
+ }
+ break;
+ case A6_REG_TS2_I2C_BAT_TEMP_LSB_MSB:
+ {
+ int32_t *out_val = (int32_t*)buf;
+ int8_t temp_val = ((int8_t*)vals)[1];
+ *out_val = temp_val * 10;
+ }
+ break;
+ default:
+ {
+ printk(KERN_ERR "%s: Invalid register %u\n", __func__, regnum);
+ ret = -EINVAL;
+ }
+ }
+
+ return ret;
+}
+
+// constraints:
+// * multi-valued stores must have individual values delimited by whitespace
+// * multi-valued stores have individual values constrained to reg size (8-bit)
+// * single-valued stores can specify 16-bit values to update two regs (msb+lsb)
+// * value count must exactly match for multi-values stores
+// * value-count for single value stores may be one less than the reg count specified.
+static ssize_t a6_generic_store(struct device *dev, struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ int32_t ret = 0, num_ids, val_cnt, i;
+ uint32_t val;
+ char *endp = NULL, *bufp;
+ uint16_t parsed_vals[id_size];
+ uint8_t in_vals[id_size];
+ struct a6_register_desc* reg_desc;
+ struct a6_device_state* state = i2c_get_clientdata(client);
+
+ // critsec for manipulating flags
+ ret = mutex_lock_interruptible(&state->dev_mutex);
+ if (ret) {
+ printk(KERN_ERR "%s: mutex_lock_interruptible interrupted(1)\n", __func__);
+ return -ERESTARTSYS;
+ }
+
+ // are we busy?
+ while (test_and_set_bit(DEVICE_BUSY_BIT, state->flags)) {
+ // yes: are we in bootload phase?
+ if (!test_bit(BOOTLOAD_ACTIVE_BIT, state->flags)) {
+ // no: so go ahead and allow concurrent i2c ops (these are
+ // synchronized separately to allow priority based execution).
+ break;
+ }
+
+ // in bootload phase: get on a waitq
+ mutex_unlock(&state->dev_mutex);
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: about to wait for device non-busy...\n", __func__);
+
+ // bootload bit set? wait to be cleared (at least 5 mins: in jiffies)
+ ret = wait_event_interruptible_timeout(state->dev_busyq,
+ !test_bit(BOOTLOAD_ACTIVE_BIT, state->flags), 300*HZ);
+ if (!ret) {
+ printk(KERN_ERR "%s: wait on device busy timed-out/interrupted\n", __func__);
+ // reset busy state
+ clear_bit(DEVICE_BUSY_BIT, state->flags);
+ // and continue...
+ }
+
+ // we're about to manipulate flags again: acquire critsec
+ ret = mutex_lock_interruptible(&state->dev_mutex);
+ if (ret) {
+ printk(KERN_ERR "%s: mutex_lock_interruptible interrupted(2)\n", __func__);
+ return -ERESTARTSYS;
+ }
+ }
+
+ // increment busy refcount
+ state->busy_count++;
+ // done with flags: exit critsec
+ mutex_unlock(&state->dev_mutex);
+
+ reg_desc = container_of(attr, struct a6_register_desc, dev_attr);
+ num_ids = reg_desc->num_ids;
+
+ if (reg_desc->r_format)
+ {
+ ret = reg_desc->r_format(state, buf, in_vals, count);
+ }
+ else
+ {
+ bufp = (char*)buf;
+ val_cnt = 0;
+ do {
+ // let strtoul perform base determination (handles '0x' and '0' prefixes) ...
+ val = (uint16_t)simple_strtoul(bufp, &endp, 0);
+ // each space-delimited write unit is capped at 16 bits...
+ if (val > 0xffff) {
+ ret = -EINVAL;
+ goto err0;
+ }
+
+ parsed_vals[val_cnt] = val;
+
+ // skip whitespace
+ while (*endp && isspace(*endp)) {
+ endp++;
+ }
+ // reset for next iteration
+ bufp = endp;
+ } while ((++val_cnt < num_ids) && ((endp - buf) < count));
+
+ // three levels of value count calidation:
+ // * if extra values: fail
+ // * if multiple values and does not match reg count specified: fail
+ // * if single value and reg count > 2: fail
+ if (*endp) {
+ ret = -EINVAL;
+ goto err0;
+ }
+ else if (val_cnt > 1) {
+ if (val_cnt != num_ids) {
+ ret = -EINVAL;
+ goto err0;
+ }
+ }
+ else {
+ if (num_ids > 2) {
+ ret = -EINVAL;
+ goto err0;
+ }
+ }
+
+ // if multi-valued store or single-valued store to one reg: we constrain each
+ // value to reg size (8 bits)
+ if ((val_cnt > 1) || (1 == num_ids)) {
+ for (i = 0; i < val_cnt; i++) {
+ if (parsed_vals[i] > 0xff) {
+ ret = -EINVAL;
+ goto err0;
+ }
+
+ in_vals[i] = parsed_vals[i];
+ }
+ }
+ // single-valued store can be 8-bit or 16-bit (for msb+lsb)
+ else {
+ ((uint16_t*)in_vals)[0] = ((uint16_t*)parsed_vals)[0];
+ }
+ }
+
+#ifdef A6_DEBUG
+ {
+ uint8_t d_ids[reg_desc->num_ids * (4+2+1)];
+ uint8_t d_vals[reg_desc->num_ids * (2+2+1)];
+ int32_t i = 0, ret_ids = 0, ret_vals = 0;
+
+ while (i < reg_desc->num_ids) {
+ ret_ids = sprintf(d_ids+ret_ids, "0x%04x ", reg_desc->id[i]);
+ ret_vals = sprintf(d_vals+ret_vals, "0x%02x ", in_vals[i]);
+ i++;
+ }
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR,
+ "storing reg name: %s, num_ids: %d, ids: %s, vals: %s\n",
+ reg_desc->debug_name, reg_desc->num_ids, d_ids, d_vals);
+ }
+#endif
+
+ ret = a6_i2c_write_reg(client, reg_desc->id, num_ids, in_vals);
+ if (ret < 0) {
+ goto err0;
+ }
+
+ ret = count;
+
+err0:
+ // reset busy state
+ mutex_lock(&state->dev_mutex);
+ // decrement busy refcount
+ if (state->busy_count) {
+ state->busy_count--;
+ }
+ if (!state->busy_count) {
+ clear_bit(DEVICE_BUSY_BIT, state->flags);
+ }
+ mutex_unlock(&state->dev_mutex);
+ wake_up_interruptible(&state->dev_busyq);
+
+ return ret;
+}
+
+enum {
+ ACTIVATE_EXTRACT,
+ NONE
+};
+
+static ssize_t a6_val_cksum_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ int32_t rc = 0, reloop = 0, failed = 0;
+ struct a6_device_state* state = i2c_get_clientdata(client);
+ uint16_t cksum1, cksum2, cksum_cycles, cksum_errors;
+ struct a6_register_desc *reg_desc;
+ uint8_t vals[id_size];
+
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: enter\n", __func__);
+
+ // critsec for manipulating flags
+ rc = mutex_lock_interruptible(&state->dev_mutex);
+ if (rc) {
+ printk(KERN_ERR "%s: mutex_lock interrupted\n", __func__);
+ return -EIO;
+ }
+
+ // are we busy?
+ while (test_and_set_bit(DEVICE_BUSY_BIT, state->flags)) {
+ // yes: get on a waitq
+ mutex_unlock(&state->dev_mutex);
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: about to wait for device non-busy...\n", __func__);
+
+ // busy bit set? wait to be cleared (at least 5 minutes: in jiffies)
+ rc = wait_event_interruptible_timeout(state->dev_busyq,
+ !test_bit(DEVICE_BUSY_BIT, state->flags), 300*HZ);
+ if (!rc) {
+ printk(KERN_ERR "%s: wait on device busy timed-out/interrupted\n", __func__);
+ // reset busy state
+ clear_bit(DEVICE_BUSY_BIT, state->flags);
+ // and continue...
+ }
+
+ // we're about to manipulate flags again: acquire critsec
+ rc = mutex_lock_interruptible(&state->dev_mutex);
+ if (rc) {
+ printk(KERN_ERR "%s: mutex_lock interrupted(2)\n", __func__);
+ goto err0;
+ }
+ }
+
+ rc = test_and_set_bit(BOOTLOAD_ACTIVE_BIT, state->flags);
+ ASSERT(!rc);
+
+ // we're done with flags: exit critsec
+ mutex_unlock(&state->dev_mutex);
+
+ do {
+ /* write command (cksum1) */
+ reg_desc = &a6_register_desc_arr[35];
+ vals[0] = TS2_I2C_COMMAND_FRAM_CHECKSUM_READ_1;
+ rc = a6_i2c_write_reg(state->i2c_dev, reg_desc->id, reg_desc->num_ids, vals);
+ if (rc < 0) {
+ printk(KERN_ERR "%s: error writing reg: %s, id: 0x%x\n",
+ __func__, reg_desc->debug_name, reg_desc->id[0]);
+ break;
+ }
+
+ /* read cksum1 */
+ reg_desc = &a6_register_desc_arr[80];
+ memset(vals, 0, sizeof(vals));
+ rc = a6_i2c_read_reg(state->i2c_dev, reg_desc->id, reg_desc->num_ids, vals);
+ if (rc < 0) {
+ printk(KERN_ERR "%s: error reading reg: %s, ids: 0x%x 0x%x\n",
+ __func__, reg_desc->debug_name, reg_desc->id[0], reg_desc->id[1]);
+ break;
+ }
+ cksum1 = vals[0] | vals[1] << 8;
+
+ /* read cksum cycle counter */
+ reg_desc = &a6_register_desc_arr[81];
+ memset(vals, 0, sizeof(vals));
+ rc = a6_i2c_read_reg(state->i2c_dev, reg_desc->id, reg_desc->num_ids, vals);
+ if (rc < 0) {
+ printk(KERN_ERR "%s: error reading reg: %s, ids: 0x%x 0x%x\n",
+ __func__, reg_desc->debug_name, reg_desc->id[0], reg_desc->id[1]);
+ break;
+ }
+ cksum_cycles = vals[0] | vals[1] << 8;
+
+ /* write command (cksum2) */
+ reg_desc = &a6_register_desc_arr[35];
+ vals[0] = TS2_I2C_COMMAND_FRAM_CHECKSUM_READ_2;
+ rc = a6_i2c_write_reg(state->i2c_dev, reg_desc->id, reg_desc->num_ids, vals);
+ if (rc < 0) {
+ printk(KERN_ERR "%s: error writing reg: %s, id: 0x%x\n",
+ __func__, reg_desc->debug_name, reg_desc->id[0]);
+ break;
+ }
+
+ /* read cksum2 */
+ reg_desc = &a6_register_desc_arr[80];
+ memset(vals, 0, sizeof(vals));
+ rc = a6_i2c_read_reg(state->i2c_dev, reg_desc->id, reg_desc->num_ids, vals);
+ if (rc < 0) {
+ printk(KERN_ERR "%s: error reading reg: %s, ids: 0x%x 0x%x\n",
+ __func__, reg_desc->debug_name, reg_desc->id[0], reg_desc->id[1]);
+ break;
+ }
+ cksum2 = vals[0] | vals[1] << 8;
+
+ /* read cksum error counter */
+ reg_desc = &a6_register_desc_arr[81];
+ memset(vals, 0, sizeof(vals));
+ rc = a6_i2c_read_reg(state->i2c_dev, reg_desc->id, reg_desc->num_ids, vals);
+ if (rc < 0) {
+ printk(KERN_ERR "%s: error reading reg: %s, ids: 0x%x 0x%x\n",
+ __func__, reg_desc->debug_name, reg_desc->id[0], reg_desc->id[1]);
+ break;
+ }
+ cksum_errors = vals[0] | vals[1] << 8;
+
+ /* validate cksum */
+ if (reloop || !cksum1 || !cksum2 || (cksum1 != cksum2)) {
+ printk(KERN_ERR "A6 checksum (%s) validation failure:\n"
+ "cksum1: 0x%02hx; cksum2: 0x%02hx; cksum_cycles: 0x%02hx;"
+ " cksum_errors: 0x%02hx\n",
+ (!reloop) ? "first-stage" : "second-stage",
+ cksum1, cksum2, cksum_cycles, cksum_errors);
+ if (reloop) {
+ reloop--;
+ failed = 1;
+ }
+ else {
+ reloop++;
+ }
+ }
+ } while (reloop);
+
+ if (rc < 0) {
+ printk(KERN_ERR "%s: checksum retrieval enountered i2c errors: "
+ "fallback to sbw(jtag) access.\n", __func__);
+ get_checksum_data_sbw((struct a6_sbw_interface*)state->plat_data->sbw_ops, &cksum1,
+ &cksum2, &cksum_cycles, &cksum_errors);
+ }
+
+ /* validate cksum */
+ if (failed || !cksum1 || !cksum2 || (cksum1 != cksum2)) {
+ printk(KERN_ERR "A6 checksum validation failed:\n"
+ "cksum1: 0x%02hx; cksum2: 0x%02hx; cksum_cycles: 0x%02hx;"
+ " cksum_errors: 0x%02hx\n",
+ cksum1, cksum2, cksum_cycles, cksum_errors);
+ rc = snprintf(buf, PAGE_SIZE, "%d\n", 0);
+ }
+ else {
+ rc = snprintf(buf, PAGE_SIZE, "%d\n", 1);
+ }
+
+err0:
+ mutex_lock(&state->dev_mutex);
+ clear_bit(BOOTLOAD_ACTIVE_BIT, state->flags);
+ clear_bit(DEVICE_BUSY_BIT, state->flags);
+ mutex_unlock(&state->dev_mutex);
+ wake_up_interruptible(&state->dev_busyq);
+
+ return rc;
+}
+
+
+static char* activation_strlist[] = {
+ [ACTIVATE_EXTRACT] = "extract",
+ [NONE] = "none"
+};
+
+static ssize_t a6_diag_store(struct device *dev, struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ int32_t rc = 0, ret_val, action_i = 0;
+ struct a6_device_state* state = i2c_get_clientdata(client);
+ bool skip = false;
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: enter\n", __func__);
+
+ action_i = (sizeof(activation_strlist)/sizeof(char*));
+ do {
+ if (0 == strncmp(buf, activation_strlist[action_i-1],
+ strlen(activation_strlist[action_i-1]))) {
+ break;
+ }
+ } while(action_i--);
+
+ if (!action_i) {
+ return -EINVAL;
+ }
+
+ // store the action index
+ action_i--;
+
+ // critsec for manipulating flags
+ rc = mutex_lock_interruptible(&state->dev_mutex);
+ if (rc) {
+ printk(KERN_ERR "%s: mutex_lock interrupted\n", __func__);
+ return -EIO;
+ }
+
+ // are we busy?
+ while (test_and_set_bit(DEVICE_BUSY_BIT, state->flags)) {
+ // yes: get on a waitq
+ mutex_unlock(&state->dev_mutex);
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: about to wait for device non-busy...\n", __func__);
+
+ // busy bit set? wait to be cleared (at least 5 minutes: in jiffies)
+ rc = wait_event_interruptible_timeout(state->dev_busyq,
+ !test_bit(DEVICE_BUSY_BIT, state->flags), 300*HZ);
+ if (!rc) {
+ printk(KERN_ERR "%s: wait on device busy timed-out/interrupted\n", __func__);
+ // reset busy state
+ clear_bit(DEVICE_BUSY_BIT, state->flags);
+ // and continue...
+ }
+
+ // we're about to manipulate flags again: acquire critsec
+ rc = mutex_lock_interruptible(&state->dev_mutex);
+ if (rc) {
+ printk(KERN_ERR "%s: mutex_lock interrupted(2)\n", __func__);
+ goto err0;
+ }
+ }
+
+ if (ACTIVATE_EXTRACT == action_i) {
+ if (test_bit(EXTRACT_INITIATED, state->flags)) {
+ skip = true;
+ }
+ else {
+ ret_val = test_and_set_bit(BOOTLOAD_ACTIVE_BIT, state->flags);
+ ASSERT(!ret_val);
+ ret_val = test_and_set_bit(EXTRACT_INITIATED, state->flags);
+ ASSERT(!ret_val);
+ }
+ }
+ else if (NONE == action_i) {
+ if (!(test_bit(EXTRACT_INITIATED, state->flags))) {
+ skip = true;
+ }
+ }
+ else {
+ // invalid
+ }
+
+ // we're done with flags: exit critsec
+ mutex_unlock(&state->dev_mutex);
+
+ if (true == skip) goto err0;
+
+ if (ACTIVATE_EXTRACT == action_i) {
+ // reset force-wake state to always force wake on first i2c txn
+ // after pmem extraction
+ del_timer(&state->a6_force_wake_timer);
+ mutex_lock(&state->a6_force_wake_mutex);
+ if (test_bit(CAP_PERIODIC_WAKE, state->flags)) {
+ clear_bit(FORCE_WAKE_ACTIVE_BIT, state->flags);
+ }
+ mutex_unlock(&state->a6_force_wake_mutex);
+
+ // start pmem extract
+ printk(KERN_ERR "%s: starting ttf_extract.\n", __func__);
+ rc = ttf_extract_fw_sbw((struct a6_sbw_interface*)state->plat_data->sbw_ops);
+ if (rc) {
+ printk(KERN_ERR "Failed to ttf_extract a6 pmem.\n");
+ goto err0;
+ }
+ else {
+ printk(KERN_ERR "A6: Completed ttf_extract a6 pmem.\n");
+ // wait for the A6 to boot...
+ msleep(3000);
+ // - re-init state
+ // - if init fails: ignore
+ a6_init_state(state->i2c_dev);
+ }
+ }
+ else if (NONE == action_i) {
+ printk(KERN_ERR "%s: clearing ttf_extract cache.\n", __func__);
+ rc = ttf_extract_cache_clear();
+ }
+ else {
+ // invalid
+ }
+
+err0:
+ mutex_lock(&state->dev_mutex);
+ if (false == skip) {
+ if (rc || NONE == action_i) {
+ if (test_bit(EXTRACT_INITIATED, state->flags)) {
+ clear_bit(EXTRACT_INITIATED, state->flags);
+ }
+ }
+ if (test_bit(BOOTLOAD_ACTIVE_BIT, state->flags)) {
+ clear_bit(BOOTLOAD_ACTIVE_BIT, state->flags);
+ }
+ }
+ clear_bit(DEVICE_BUSY_BIT, state->flags);
+ mutex_unlock(&state->dev_mutex);
+ wake_up_interruptible(&state->dev_busyq);
+
+ return (rc ? rc : count);
+}
+
+static ssize_t a6_diag_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ int32_t rc = 0;
+ struct a6_device_state* state = i2c_get_clientdata(client);
+
+
+ mutex_lock(&state->dev_mutex);
+ if (test_bit(EXTRACT_INITIATED, state->flags)) {
+ rc = snprintf(buf, PAGE_SIZE, "%s\n", activation_strlist[ACTIVATE_EXTRACT]);
+ }
+ mutex_unlock(&state->dev_mutex);
+
+ return rc;
+}
+
+int32_t a6_create_dev_files(struct a6_device_state* state, struct device* dev)
+{
+ int32_t rc = 0, reg_cnt = sizeof(a6_register_desc_arr)/sizeof(struct a6_register_desc);
+ int32_t idx = 0, cust_idx = 0;
+
+ for (idx = 0; idx < reg_cnt; idx++) {
+ rc = device_create_file(dev, &a6_register_desc_arr[idx].dev_attr);
+ if (rc < 0) {
+ printk(KERN_ERR "%s: failed to create dev_attr file for %s.\n",
+ __func__, a6_register_desc_arr[idx].dev_attr.attr.name);
+ goto err0;
+ }
+ }
+
+ reg_cnt = sizeof(custom_devattr)/sizeof(struct device_attribute);
+ for (cust_idx = 0; cust_idx < reg_cnt; cust_idx++) {
+ rc = device_create_file(dev, &custom_devattr[cust_idx]);
+ if (rc < 0) {
+ printk(KERN_ERR "%s: failed to create dev_attr file for %s.\n",
+ __func__, custom_devattr[cust_idx].attr.name);
+ goto err0;
+ }
+ }
+
+ rc = sysfs_create_link(&state->mdev.this_device->kobj, &dev->kobj, "regs");
+ if (rc) {
+ printk(KERN_ERR "%s: error in creating symlink.\n", __func__);
+ }
+
+ return 0;
+
+err0:
+ // error: cleanup files already created.
+ for (idx--; idx >= 0; idx--) {
+ device_remove_file(dev, &a6_register_desc_arr[idx].dev_attr);
+ }
+ for (cust_idx--; cust_idx >= 0; cust_idx--) {
+ device_remove_file(dev, &custom_devattr[cust_idx]);
+ }
+ return rc;
+
+}
+
+void a6_remove_dev_files(struct a6_device_state* state, struct device* dev)
+{
+ int32_t reg_cnt = sizeof(a6_register_desc_arr)/sizeof(struct a6_register_desc);
+ int32_t idx;
+
+ for (idx = 0; idx < reg_cnt; idx++) {
+ device_remove_file(dev, &a6_register_desc_arr[idx].dev_attr);
+ }
+
+ reg_cnt = sizeof(custom_devattr)/sizeof(struct device_attribute);
+ for (idx = 0; idx < reg_cnt; idx++) {
+ device_remove_file(dev, &custom_devattr[idx]);
+ }
+}
+
+typedef enum {
+ A6_PROGAM_AND_VERIFY_FW = 1,
+ A6_VERIFY_FW
+} a6_pgm_thread_op;
+
+struct a6_pgm_thread_params {
+ struct a6_sbw_interface* sbw_ops;
+ uint32_t buffer_p;
+ int32_t ret_code;
+ a6_pgm_thread_op op;
+ struct completion a6_flash_thread_nice;
+ struct completion a6_flash_thread_exit;
+};
+
+int a6_pgm_thread_fn(void* param)
+{
+ int32_t ret_val;
+
+ struct a6_pgm_thread_params* t_p = (struct a6_pgm_thread_params*)param;
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: buffer_p val: 0x%x\n", __func__, t_p->buffer_p);
+
+ // wait for parent re-nice completion...
+ ret_val = wait_for_completion_interruptible(&t_p->a6_flash_thread_nice);
+ if (-ERESTARTSYS == ret_val) {
+ printk(KERN_ERR "A6: waiting for parent re-nice completion interrupted.\n");
+ t_p->ret_code = -EINTR;
+ goto err0;
+ }
+
+ if (A6_PROGAM_AND_VERIFY_FW == t_p->op) {
+ ret_val = program_device_sbw(t_p->sbw_ops, t_p->buffer_p);
+ }
+ else {
+ ret_val = verify_device_sbw(t_p->sbw_ops, t_p->buffer_p);
+ // ignore error returns for the moment as the verification fn
+ // does not differentiate between code and r/w data sections.
+ // also, the fw section allocation changes fairly dynamically
+ // between fw drops and we don't yet support parameterizing the
+ // section map for the verification code.
+ ret_val = 0;
+ }
+ if (ret_val) {
+ t_p->ret_code = -EINVAL;
+ }
+
+ // signal thread completion
+ complete(&t_p->a6_flash_thread_exit);
+
+err0:
+ return 0;
+}
+
+static long a6_ioctl(struct file *file,
+ unsigned int cmd, unsigned long args)
+{
+ int32_t rc = 0;
+ struct a6_device_state* state = file->private_data;
+ void* usr_ptr = (void*)args;
+ //uint32_t usr_bytes = _IOC_SIZE(cmd);
+ //uint32_t usr_val = 0x0;
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: cmd: %d\n", __func__, _IOC_NR(cmd));
+
+ // critsec for manipulating flags
+ rc = mutex_lock_interruptible(&state->dev_mutex);
+ if (rc) {
+ printk(KERN_ERR "%s: mutex_lock interrupted(1)\n", __func__);
+ return -ERESTARTSYS;
+ }
+
+ // are we busy?
+ while (test_and_set_bit(DEVICE_BUSY_BIT, state->flags)) {
+ // yes: get on a waitq
+ mutex_unlock(&state->dev_mutex);
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: about to wait for device non-busy...\n", __func__);
+
+ // busy bit set? wait to be cleared (at least 3 seconds: in jiffies)
+ rc = wait_event_interruptible_timeout(state->dev_busyq,
+ !test_bit(DEVICE_BUSY_BIT, state->flags), 3*HZ);
+ if (!rc) {
+ printk(KERN_ERR "%s: wait on device busy timed-out/interrupted\n", __func__);
+ // reset busy state
+ clear_bit(DEVICE_BUSY_BIT, state->flags);
+ // and continue...
+ }
+
+ // we're about to manipulate flags again: acquire critsec
+ rc = mutex_lock_interruptible(&state->dev_mutex);
+ if (rc) {
+ printk(KERN_ERR "%s: mutex_lock interrupted(2)\n", __func__);
+ return -ERESTARTSYS;
+ }
+ }
+
+ // bootload request: set flag in critsec
+ if (A6_IOCTL_SET_FW_DATA == cmd || A6_IOCTL_VERIFY_FW_DATA == cmd) {
+ int32_t ret_val;
+ ret_val = test_and_set_bit(BOOTLOAD_ACTIVE_BIT, state->flags);
+ ASSERT(!ret_val);
+ }
+
+ // we're done with flags: exit critsec
+ mutex_unlock(&state->dev_mutex);
+
+ switch (cmd) {
+ case A6_IOCTL_SET_FW_DATA:
+ case A6_IOCTL_VERIFY_FW_DATA:
+ {
+ uint8_t *buffer_p = NULL;
+ uint32_t payload_size = 0;
+ struct a6_sbw_interface* sbw_ops =
+ (struct a6_sbw_interface*)state->plat_data->sbw_ops;
+ uint8_t* a6_fw_buffer;
+ struct task_struct* pgm_worker_task;
+ pid_t pgm_worker_task_pid;
+ struct a6_pgm_thread_params t_params;
+
+ // reset force-wake state to always force wake on first i2c txn
+ // after flashing/verification
+ del_timer(&state->a6_force_wake_timer);
+ mutex_lock(&state->a6_force_wake_mutex);
+ if (test_bit(CAP_PERIODIC_WAKE, state->flags)) {
+ clear_bit(FORCE_WAKE_ACTIVE_BIT, state->flags);
+ }
+ mutex_unlock(&state->a6_force_wake_mutex);
+
+ // copy payload ptr from ioctl parameter
+ if (copy_from_user(&buffer_p, usr_ptr, sizeof(buffer_p))) {
+ rc = -EFAULT;
+ break;
+ }
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "buffer_p val: 0x%p\n", buffer_p);
+
+ // copy payload size from ioctl parameter
+ if (copy_from_user(&payload_size, (uint8_t*)usr_ptr + sizeof(uint32_t), sizeof(payload_size))) {
+ rc = -EFAULT;
+ break;
+ }
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "payload_size: %d\n", payload_size);
+
+ // alloc kernel buffer for payload
+ a6_fw_buffer = (uint8_t*)kmalloc(payload_size, GFP_KERNEL);
+ if (!a6_fw_buffer) {
+ printk(KERN_ERR "A6: failed to allocate fw buffer.\n");
+ rc = -ENOMEM;
+ break;
+ }
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "fw buffer ptr_val: 0x%p\n", a6_fw_buffer);
+
+ // copy-in payload data
+ if (copy_from_user(a6_fw_buffer, buffer_p, payload_size)) {
+ rc = -EFAULT;
+ break;
+ }
+
+ printk(KERN_ERR "A6: Starting flashing sequence.\n");
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "A6: parent task nice value: %d; pri: %d\n",
+ task_nice(current), task_prio(current));
+
+ // initialize worker task params
+ init_completion(&t_params.a6_flash_thread_nice);
+ init_completion(&t_params.a6_flash_thread_exit);
+ t_params.sbw_ops = sbw_ops;
+ t_params.buffer_p = (uint32_t)a6_fw_buffer;
+ t_params.ret_code = 0;
+ t_params.op = (A6_IOCTL_SET_FW_DATA == cmd) ?
+ A6_PROGAM_AND_VERIFY_FW : A6_VERIFY_FW;
+
+ // create worker task...
+ pgm_worker_task_pid = kernel_thread(a6_pgm_thread_fn, &t_params,
+ CLONE_KERNEL);
+ if (pgm_worker_task_pid < 0) {
+ printk(KERN_ERR "A6: failed to create pgm worker task.\n");
+ rc = -EIO;
+ break;
+ }
+
+ // retrieve worker task struct
+ pgm_worker_task = get_pid_task(find_get_pid(pgm_worker_task_pid), PIDTYPE_PID);
+ // re-nice worker task
+ //set_user_nice(pgm_worker_task, 10);
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "A6: pgm worker task nice value: %d; pri: %d\n",
+ task_nice(pgm_worker_task), task_prio(pgm_worker_task));
+
+ /* hold cpu at max freq before signaling worker thread to commence sbw */
+#ifdef CONFIG_CPU_FREQ_GOV_ONDEMAND_TICKLE
+ CPUFREQ_HOLD_CHECK(&state->cpufreq_hold_flag);
+#endif
+ // signal re-nice completion...
+ complete(&t_params.a6_flash_thread_nice);
+
+ // wait for worker task-end completion...
+ rc = wait_for_completion_interruptible(&t_params.a6_flash_thread_exit);
+ /* unhold cpu */
+#ifdef CONFIG_CPU_FREQ_GOV_ONDEMAND_TICKLE
+ CPUFREQ_UNHOLD_CHECK(&state->cpufreq_hold_flag);
+#endif
+ kfree(a6_fw_buffer);
+
+ if (-ERESTARTSYS == rc) {
+ printk(KERN_ERR "A6: waiting for pgm worker start interrupted.\n");
+ break;
+ }
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "A6: pgm worker task exit code: %d\n",
+ t_params.ret_code);
+ if (t_params.ret_code) {
+ printk(KERN_ERR "A6: Failed to completed flashing sequence. ret: %d\n",
+ t_params.ret_code);
+ // propagate error to caller...
+ rc = t_params.ret_code;
+ }
+ else {
+ printk(KERN_ERR "A6: Completed flashing sequence.\n");
+ // wait for the A6 to boot...
+ msleep(3000);
+ // - flashing fw forces a device power-up sequence: re-init state
+ // - if init fails: treat as fw flash failure by returning rc
+ rc = a6_init_state(state->i2c_dev);
+ if (rc < 0) {
+ printk(KERN_ERR "%s: failed to initialize, err: %d\n", A6_DRIVER, rc);
+ }
+ }
+
+ mutex_lock(&state->dev_mutex);
+ clear_bit(BOOTLOAD_ACTIVE_BIT, state->flags);
+ mutex_unlock(&state->dev_mutex);
+ }
+ break;
+
+ default:
+ {
+ rc = -EINVAL;
+ }
+ break;
+ }
+
+
+//Done:
+ mutex_lock(&state->dev_mutex);
+ if (rc) {
+ if (test_bit(BOOTLOAD_ACTIVE_BIT, state->flags)) {
+ clear_bit(BOOTLOAD_ACTIVE_BIT, state->flags);
+ }
+ clear_bit(DEVICE_BUSY_BIT, state->flags);
+ }
+ else {
+ if (!test_bit(BOOTLOAD_ACTIVE_BIT, state->flags)) {
+ clear_bit(DEVICE_BUSY_BIT, state->flags);
+ }
+ }
+ mutex_unlock(&state->dev_mutex);
+
+ wake_up_interruptible(&state->dev_busyq);
+
+ return rc;
+}
+
+static int a6_open(struct inode *inode, struct file *file)
+{
+ struct a6_device_state* state;
+
+ /* get device */
+ state = container_of(file->f_op, struct a6_device_state, fops);
+
+ /* Allow only read. */
+ //if ((file->f_mode & (FMODE_READ|FMODE_WRITE)) != FMODE_READ) {
+ // return -EINVAL;
+ //}
+
+ ///* check if it is in use */
+ //if (test_and_set_bit(IS_OPENED, state->flags)) {
+ // return -EBUSY;
+ //}
+
+ /* attach private data */
+ file->private_data = state;
+
+ return 0;
+}
+
+static int a6_close(struct inode *inode, struct file *file)
+{
+ struct a6_device_state* state = (struct a6_device_state*) file->private_data;
+
+ state = state;
+ ///* mark it as unused */
+ //clear_bit(IS_OPENED, state->flags);
+
+ return 0;
+}
+
+#define A2A_DGRAM_PREAMBLE (0x5AC35AC3)
+struct a2a_dgram_hdr {
+ uint32_t preamble;
+ uint16_t len;
+ uint16_t cksum;
+};
+
+static ssize_t a6_read(struct file *file, char __user *buf, size_t count, loff_t *ppos )
+{
+# define A2A_RX_MISS_THRESHOLD (300)
+
+ ssize_t rc = 0;
+ struct a6_device_state* state;
+ struct a6_register_desc *reg_desc_comm_status, *reg_desc_comm_rxtx;
+ uint8_t vals[id_size];
+ int32_t miss_count, rd_count;
+ //struct a2a_dgram_hdr hdr;
+ uint32_t start_time;
+ uint8_t elt_val = 0, prev_byte;
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: enter\n", __func__);
+
+ start_time = jiffies;
+ /* input validations */
+ if (!count) {
+ return -EINVAL;
+ }
+ else if (count > A2A_RD_BUFF_SIZE) {
+ return -EFBIG;
+ }
+
+ /* get state */
+ state = container_of(file->f_op, struct a6_device_state, fops);
+
+ // acquire critsec
+ rc = mutex_lock_interruptible(&state->dev_mutex);
+ if (rc) {
+ printk(KERN_ERR "%s: mutex_lock interrupted.\n", __func__);
+ return -ERESTARTSYS;
+ }
+
+ /* not connected? exit */
+ if (!test_bit(A2A_CONNECTED, state->flags)) {
+ mutex_unlock(&state->dev_mutex);
+ printk(KERN_ERR "%s: no a2a connection detected.\n", __func__);
+ return -EINVAL;
+ }
+
+ // busy?
+ if (test_and_set_bit(READ_ACTIVE_BIT, state->flags)) {
+ // yes: get on a waitq
+ mutex_unlock(&state->dev_mutex);
+ printk(KERN_ERR "%s: re-entrant call disallowed.\n", __func__);
+ return -EINVAL;
+ }
+
+ mutex_unlock(&state->dev_mutex);
+
+ /* init a2a read ptr */
+ state->a2a_rp = state->a2a_rd_buf;
+
+ rd_count = 0;
+ miss_count = 0;
+ prev_byte = 0;
+ do {
+ /* read comm status */
+ reg_desc_comm_status = &a6_register_desc_arr[63];
+ memset(vals, 0, sizeof(vals));
+ rc = a6_i2c_read_reg(state->i2c_dev, reg_desc_comm_status->id,
+ reg_desc_comm_status->num_ids, vals);
+ if (rc < 0) {
+ printk(KERN_ERR "%s: error reading reg: %s, id: 0x%x\n",
+ __func__, reg_desc_comm_status->debug_name, reg_desc_comm_status->id[0]);
+ goto err0;
+ }
+
+ if (!(vals[0] & TS2_I2C_COMM_STATUS_RX_FULL)) {
+ if (++miss_count > A2A_RX_MISS_THRESHOLD) {
+ if (test_bit(A2A_CONNECTED, state->flags)) {
+ continue;
+ }
+ else {
+ break;
+ }
+ }
+ continue;
+ }
+
+ /* read rx data */
+ memset(vals, 0, sizeof(vals));
+ reg_desc_comm_rxtx = &a6_register_desc_arr[64];
+ rc = a6_i2c_read_reg(state->i2c_dev, reg_desc_comm_rxtx->id,
+ reg_desc_comm_rxtx->num_ids, vals);
+ if (rc < 0) {
+ printk(KERN_ERR "%s: error writing reg: %s, id: 0x%x\n",
+ __func__, reg_desc_comm_rxtx->debug_name, reg_desc_comm_rxtx->id[0]);
+ rc = -EIO;
+ goto err0;
+ }
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: read byte: %d successfully.\n",
+ __func__, rd_count);
+
+ if (a6_t2s_dup_correct) {
+ if ((prev_byte & 0x80) ^ (vals[0] & 0x80)) {
+ prev_byte = vals[0];
+ if (vals[0] & 0x80) {
+ elt_val = _convert_hex_char_to_decimal(vals[0] & 0x7f) << 4;
+ continue;
+ }
+ else {
+ elt_val |= (_convert_hex_char_to_decimal(vals[0]) & 0x0f);
+ }
+ }
+ else {
+ printk(KERN_ERR "%s: t2s duplicate detected; char: 0x%02x.\n",
+ __func__, vals[0]);
+ continue;
+ }
+ }
+ else {
+ elt_val = vals[0];
+ }
+
+ *state->a2a_rp = elt_val;
+ state->a2a_rp++;
+ rd_count++;
+ } while (rd_count < count);
+
+ if (!rd_count) {
+ printk(KERN_ERR "%s: rx failed; A2A connection terminated.\n", __func__);
+ rc = -EINVAL;
+ }
+ else {
+ long diff_time;
+
+ if (copy_to_user(buf, state->a2a_rd_buf, rd_count)) {
+ rc = -EFAULT;
+ goto err0;
+ }
+ rc = rd_count;
+ diff_time = (long)jiffies - (long)start_time;
+ printk(KERN_ERR "%s: elapsed time: %ld ms; count: %u\n",
+ __func__, diff_time * 1000/HZ, rd_count);
+ }
+
+
+err0:
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: exit\n", __func__);
+ clear_bit(READ_ACTIVE_BIT, state->flags);
+ return rc;
+}
+
+static ssize_t a6_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos )
+{
+# define A2A_TX_MISS_THRESHOLD (300)
+
+ ssize_t rc = 0;
+ struct a6_device_state* state;
+ struct a6_register_desc *reg_desc_comm_status, *reg_desc_comm_rxtx;
+ uint8_t vals[id_size];
+ int32_t miss_count, wr_count;
+ //struct a2a_dgram_hdr hdr;
+ uint32_t start_time;
+ uint8_t elt_buf[2], *elt_bufp = NULL;
+ int elt_bufsize;
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: enter\n", __func__);
+
+ start_time = jiffies;
+ /* input validations */
+ if (!count) {
+ return -EINVAL;
+ }
+ else if (count > A2A_WR_BUFF_SIZE) {
+ return -EFBIG;
+ }
+
+ /* get state */
+ state = container_of(file->f_op, struct a6_device_state, fops);
+
+ // acquire critsec
+ rc = mutex_lock_interruptible(&state->dev_mutex);
+ if (rc) {
+ printk(KERN_ERR "%s: mutex_lock interrupted.\n", __func__);
+ return -ERESTARTSYS;
+ }
+
+ /* not connected? exit */
+ if (!test_bit(A2A_CONNECTED, state->flags)) {
+ mutex_unlock(&state->dev_mutex);
+ printk(KERN_ERR "%s: no a2a connection detected.\n", __func__);
+ return -EINVAL;
+ }
+
+ // busy?
+ if (test_and_set_bit(WRITE_ACTIVE_BIT, state->flags)) {
+ mutex_unlock(&state->dev_mutex);
+ printk(KERN_ERR "%s: re-entrant call disallowed.\n", __func__);
+ return -EINVAL;
+ }
+
+ mutex_unlock(&state->dev_mutex);
+
+ /* copy to kernel buffer */
+ //hdr.preamble = A2A_DGRAM_PREAMBLE;
+ //hdr.len = count;
+ //hdr.cksum = 0;
+ ///* header */
+ //if (copy_from_user(state->a2a_wr_buf, &hdr, sizeof(hdr))) {
+ // rc = -EFAULT;
+ // mutex_unlock(&state->dev_mutex);
+ // goto err0;
+ //}
+ /* data */
+ if (copy_from_user(state->a2a_wr_buf/*+sizeof(hdr)*/, buf, count)) {
+ rc = -EFAULT;
+ goto err0;
+ }
+ /* init a2a write ptr */
+ state->a2a_wp = state->a2a_wr_buf;
+
+ wr_count = 0;
+ miss_count = 0;
+ elt_bufsize = 0;
+ do {
+ /* read comm status */
+ reg_desc_comm_status = &a6_register_desc_arr[63];
+ memset(vals, 0, sizeof(vals));
+ rc = a6_i2c_read_reg(state->i2c_dev, reg_desc_comm_status->id,
+ reg_desc_comm_status->num_ids, vals);
+ if (rc < 0) {
+ printk(KERN_ERR "%s: error reading reg: %s, id: 0x%x\n",
+ __func__, reg_desc_comm_status->debug_name, reg_desc_comm_status->id[0]);
+ goto err0;
+ }
+
+ if (!(vals[0] & TS2_I2C_COMM_STATUS_TX_EMPTY)) {
+ if (++miss_count > A2A_TX_MISS_THRESHOLD) {
+ if (test_bit(A2A_CONNECTED, state->flags)) {
+ continue;
+ }
+ else {
+ break;
+ }
+ }
+ continue;
+ }
+
+ /* write tx data */
+ reg_desc_comm_rxtx = &a6_register_desc_arr[64];
+ if (!elt_bufsize) {
+ if (a6_t2s_dup_correct) {
+ elt_bufsize = 2;
+ sprintf(elt_buf, "%02x", *state->a2a_wp);
+ elt_buf[0] |= 0x80;
+ }
+ else {
+ elt_bufsize = 1;
+ elt_buf[0] = *state->a2a_wp;
+ }
+ elt_bufp = elt_buf;
+ }
+ rc = a6_i2c_write_reg(state->i2c_dev, reg_desc_comm_rxtx->id,
+ reg_desc_comm_rxtx->num_ids, elt_bufp);
+ if (rc < 0) {
+ printk(KERN_ERR "%s: error writing reg: %s, id: 0x%x\n",
+ __func__, reg_desc_comm_rxtx->debug_name, reg_desc_comm_rxtx->id[0]);
+ rc = -EIO;
+ goto err0;
+ }
+ elt_bufp++;
+ elt_bufsize--;
+
+ if (!elt_bufsize) {
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: written byte: %d successfully.\n",
+ __func__, wr_count);
+ state->a2a_wp++;
+ wr_count++;
+ }
+ } while (wr_count < count);
+
+ if (!wr_count) {
+ printk(KERN_ERR "%s: tx failed; A2A connection terminated.\n",
+ __func__);
+ rc = -EIO;
+ goto err0;
+ }
+ else {
+ long diff_time;
+ rc = wr_count;
+ diff_time = (long)jiffies - (long)start_time;
+ printk(KERN_ERR "%s: elapsed time: %ld ms; count: %u\n", __func__,
+ diff_time * 1000/HZ, wr_count);
+ }
+
+
+err0:
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: exit\n", __func__);
+ clear_bit(WRITE_ACTIVE_BIT, state->flags);
+ return rc;
+}
+
+static unsigned int a6_poll(struct file* file, struct poll_table_struct* wait)
+{
+ unsigned int mask = 0;
+
+ return mask;
+}
+
+struct file_operations a6_fops = {
+ .owner = THIS_MODULE,
+ .read = a6_read,
+ .write = a6_write,
+ .poll = a6_poll,
+ .unlocked_ioctl = a6_ioctl,
+ .open = a6_open,
+ .release = a6_close,
+};
+
+/*
+ * a6 interrupt handler
+ */
+static irqreturn_t a6_irq(int irq, void *dev_id)
+{
+ struct a6_device_state* state = (struct a6_device_state *)dev_id;
+
+ a6_tp_irq_count++;
+#if defined PROFILE_USAGE
+ /*
+ if (true == reset_active) {
+ diff_time = (long)jiffies - (long)start_time;
+ reset_active = false;
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "A6_IRQ toggle post power-cycle after: %d ms\n",
+ diff_time*1000/HZ);
+ }
+ */
+#endif
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: entry.\n", __func__);
+
+ if (test_bit(IS_SUSPENDED, state->flags)) {
+ set_bit(INT_PENDING, state->flags);
+ } else {
+ queue_work(state->ka6d_workqueue, &state->a6_irq_work);
+ }
+
+ return IRQ_HANDLED;
+}
+
+void a6_irq_work_handler(struct work_struct *work)
+{
+ struct a6_device_state* state =
+ container_of(work, struct a6_device_state, a6_irq_work);
+ struct a6_register_desc *reg_desc_status3, *reg_desc_status2;
+ uint8_t vals[id_size], reg_val_status3 = 0, reg_val_status2 = 0;
+ bool charge_source_changed = false;
+ bool battery_changed = false;
+ int32_t ret = 0;
+ char *envp[] = {
+ [0] = "A6_ACTION=EMERGENCY_RESET_NOTIFY",
+ [1] = NULL,
+ [2] = "A6_ACTION=LOG_THRESHOLD_NOTIFY",
+ [3] = NULL,
+ [4] = "A6_ACTION=CHARGE_SOURCE_NOTIFY",
+ [5] = NULL,
+ [6] = "A6_ACTION=PERCENT_LOW_WARN1_NOTIFY",
+ [7] = NULL,
+ [8] = "A6_ACTION=PERCENT_LOW_WARN2_NOTIFY",
+ [9] = NULL,
+ [10] = "A6_ACTION=PECENT_LOW_CRIT_NOTIFY",
+ [11] = NULL,
+ [12] = "A6_ACTION=VOLTAGE_LOW_CRIT_NOTIFY",
+ [13] = NULL,
+ [14] = "A6_ACTION=TEMP_LOW_CRIT_NOTIFY",
+ [15] = NULL,
+ [16] = "A6_ACTION=TEMP_HIGH_CRIT_NOTIFY",
+ [17] = NULL,
+ [18] = "A6_ACTION=A2A_CONNECT_NOTIFY",
+ [19] = NULL
+ };
+
+ // block irq processing while a6 fw flashing is in progress because i2c requests
+ // will fail anyway and we dont' want to fiddle with SBW_WKUP while flashing is
+ // in progress...
+
+ // critsec for manipulating flags
+ mutex_lock(&state->dev_mutex);
+
+ // busy?
+ while (test_and_set_bit(DEVICE_BUSY_BIT, state->flags)) {
+ // yes: are we in bootload phase?
+ if (!test_bit(BOOTLOAD_ACTIVE_BIT, state->flags)) {
+ // no: so go ahead and allow concurrent i2c ops (these are
+ // synchronized separately to allow priority based execution).
+ break;
+ }
+
+ // in bootload phase: get on a waitq
+ mutex_unlock(&state->dev_mutex);
+ printk(KERN_ERR "%s: about to wait for device non-busy...\n", __func__);
+
+ // bootload bit set? wait to be cleared (at least 5 mins: in jiffies)
+ ret = wait_event_interruptible_timeout(state->dev_busyq,
+ !test_bit(BOOTLOAD_ACTIVE_BIT, state->flags), 300*HZ);
+ if (!ret) {
+ printk(KERN_ERR "%s: wait on device busy timed-out/interrupted\n", __func__);
+ // reset busy state
+ clear_bit(BOOTLOAD_ACTIVE_BIT, state->flags);
+ clear_bit(DEVICE_BUSY_BIT, state->flags);
+ // and continue...
+ }
+
+ // we're about to manipulate flags again: acquire critsec
+ mutex_lock(&state->dev_mutex);
+ }
+
+ /* increment busy refcount */
+ state->busy_count++;
+
+ // done with flags: exit critsec
+ mutex_unlock(&state->dev_mutex);
+
+ /* determine irq cause */
+ reg_desc_status3 = &a6_register_desc_arr[7];
+ reg_desc_status2 = &a6_register_desc_arr[6];
+
+ /* (1) read int_status3: emergency reset and charge-source change */
+ memset(vals, 0, sizeof(vals));
+ ret = a6_i2c_read_reg(state->i2c_dev, reg_desc_status3->id, reg_desc_status3->num_ids, vals);
+ if (ret < 0) {
+ printk(KERN_ERR "%s: error reading reg: %s, id: 0x%x\n",
+ __func__, reg_desc_status3->debug_name, reg_desc_status3->id[0]);
+ goto err0;
+ }
+ reg_val_status3 = vals[0];
+
+ /* (2) read int_status2: other irq causes */
+ memset(vals, 0, sizeof(vals));
+ ret = a6_i2c_read_reg(state->i2c_dev, reg_desc_status2->id, reg_desc_status2->num_ids, vals);
+ if (ret < 0) {
+ printk(KERN_ERR "%s: error reading reg: %s, id: 0x%x\n",
+ __func__, reg_desc_status2->debug_name, reg_desc_status2->id[0]);
+ goto err0;
+ }
+ reg_val_status2 = vals[0];
+
+ /* (3) now clear all status bits: this "releases" the irq line early */
+ if (reg_val_status3) {
+ vals[0] = reg_val_status3;
+ ret = a6_i2c_write_reg(state->i2c_dev, reg_desc_status3->id, reg_desc_status3->num_ids, vals);
+ if (ret < 0) {
+ printk(KERN_ERR "%s: error writing reg: %s, id: 0x%x\n",
+ __func__, reg_desc_status3->debug_name, reg_desc_status3->id[0]);
+ goto err0;
+ }
+ }
+ if (reg_val_status2) {
+ vals[0] = reg_val_status2;
+ ret = a6_i2c_write_reg(state->i2c_dev, reg_desc_status2->id, reg_desc_status2->num_ids, vals);
+ if (ret < 0) {
+ printk(KERN_ERR "%s: error writing reg: %s, id: 0x%x\n",
+ __func__, reg_desc_status3->debug_name, reg_desc_status3->id[0]);
+ goto err0;
+ }
+ }
+
+ /* (4) process emergency reset and charge-source changes...*/
+ if (reg_val_status3) {
+ /* emergency reset? */
+ if (reg_val_status3 & TS2_I2C_INT_3_RESET) {
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: emergency reset detected.\n",
+ __func__);
+
+ /* Send uevent */
+ kobject_uevent_env(&state->i2c_dev->dev.kobj, KOBJ_CHANGE, &envp[0]);
+ /* this condition overrides all others and we can probably skip
+ resetting the status bits */
+ goto err0;
+ }
+
+ /* a2a connect change? */
+ if (reg_val_status3 & TS2_I2C_INT_3_A2A_CONNECT_CHANGE) {
+ struct a6_register_desc *reg_desc_charger;
+ uint8_t chg_vals[id_size];
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: a2a connect change detected.\n",
+ __func__);
+ printk(KERN_ERR "%s: a2a connect change detected.\n", __func__);
+ reg_desc_charger = &a6_register_desc_arr[31];
+ memset(chg_vals, 0, sizeof(chg_vals));
+ if (a6_i2c_read_reg(state->i2c_dev, reg_desc_charger->id,
+ reg_desc_charger->num_ids, chg_vals) < 0) {
+ printk(KERN_ERR "%s: error reading reg: %s, id: 0x%x\n",
+ __func__, reg_desc_charger->debug_name,
+ reg_desc_charger->id[0]);
+ }
+ else {
+ if (chg_vals[0] & TS2_I2C_FLAGS_2_A2A_CONNECT) {
+ set_bit(A2A_CONNECTED, state->flags);
+ }
+ else {
+ clear_bit(A2A_CONNECTED, state->flags);
+ }
+ /* Send uevent */
+ kobject_uevent_env(&state->i2c_dev->dev.kobj, KOBJ_CHANGE, &envp[18]);
+ }
+ }
+
+ /* charger-source change? */
+ if (reg_val_status3 & TS2_I2C_INT_3_FLAGS_CHANGE) {
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: charger-source change detected.\n",
+ __func__);
+
+ /* next, unblock task on charger_source_notify node */
+ //sysfs_notify_dirent(state->notify_nodes[DIRENT_CHG_SRC_NOTIFY]);
+ /* Send uevent */
+ kobject_uevent_env(&state->i2c_dev->dev.kobj, KOBJ_CHANGE, &envp[4]);
+ charge_source_changed = true;
+ }
+
+ /* log threshold change? */
+ if (reg_val_status3 & TS2_I2C_INT_3_LOG) {
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: log threshold detected.\n",
+ __func__);
+
+ /* Send uevent */
+ kobject_uevent_env(&state->i2c_dev->dev.kobj, KOBJ_CHANGE, &envp[2]);
+ }
+ }
+
+ /* (5) process other irq causes... */
+ if (reg_val_status2) {
+ /* battery low critical? */
+ if (reg_val_status2 & TS2_I2C_INT_2_BAT_RARC_CRIT) {
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: battery low critical detected.\n",
+ __func__);
+
+ /* Send uevent */
+ kobject_uevent_env(&state->i2c_dev->dev.kobj, KOBJ_CHANGE, &envp[10]);
+ battery_changed = true;
+ }
+
+ /* battery voltage low critical? */
+ if (reg_val_status2 & TS2_I2C_INT_2_BAT_VOLT_LOW) {
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: battery voltage low critical detected.\n",
+ __func__);
+
+ /* Send uevent */
+ kobject_uevent_env(&state->i2c_dev->dev.kobj, KOBJ_CHANGE, &envp[12]);
+ battery_changed = true;
+ }
+
+ /* battery temp high critical? */
+ if (reg_val_status2 & TS2_I2C_INT_2_BAT_TEMP_HIGH) {
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: battery temp high critical detected.\n",
+ __func__);
+
+ /* Send uevent */
+ kobject_uevent_env(&state->i2c_dev->dev.kobj, KOBJ_CHANGE, &envp[16]);
+ }
+
+ /* battery temp low critical? */
+ if (reg_val_status2 & TS2_I2C_INT_2_BAT_TEMP_LOW) {
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: battery temp low critical detected.\n",
+ __func__);
+
+ /* Send uevent */
+ kobject_uevent_env(&state->i2c_dev->dev.kobj, KOBJ_CHANGE, &envp[14]);
+ }
+
+ /* battery low percent warn2? */
+ if (reg_val_status2 & TS2_I2C_INT_2_BAT_RARC_LOW2) {
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: battery low percent warn2 detected.\n",
+ __func__);
+
+ /* Send uevent */
+ kobject_uevent_env(&state->i2c_dev->dev.kobj, KOBJ_CHANGE, &envp[8]);
+ battery_changed = true;
+ }
+
+ /* battery low percent warn1? */
+ if (reg_val_status2 & TS2_I2C_INT_2_BAT_RARC_LOW1) {
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: battery low percent warn1 detected.\n",
+ __func__);
+
+ /* Send uevent */
+ kobject_uevent_env(&state->i2c_dev->dev.kobj, KOBJ_CHANGE, &envp[6]);
+ battery_changed = true;
+ }
+ }
+
+err0:
+ /* decrement busy refcount */
+ if (state->busy_count)
+ state->busy_count--;
+ if (!state->busy_count)
+ clear_bit(DEVICE_BUSY_BIT, state->flags);
+
+ if (charge_source_changed) {
+ a6_dock_update_state(state);
+ }
+ if (state->plat_data->power_supply_connected == 1 && batt_state != NULL) {
+ if (battery_changed) {
+ power_supply_changed(&a6_fish_power_supplies[0]);
+ }
+ if (charge_source_changed) {
+ power_supply_changed(&a6_fish_power_supplies[1]);
+ power_supply_changed(&a6_fish_power_supplies[2]);
+#ifdef CONFIG_A6_ENABLE_DOCK_PS
+ power_supply_changed(&a6_fish_power_supplies[3]);
+#endif
+ a6_update_connected_ps();
+ }
+ }
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: Visited\n", __func__);
+}
+
+
+#ifdef A6_PQ
+int32_t a6_stop_ai_dispatch_task(struct a6_device_state* state)
+{
+ int32_t rc = 0;
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: entered.\n", __func__);
+ // critsec for manipulating flags
+ rc = mutex_lock_interruptible(&state->dev_mutex);
+ if (rc) {
+ printk(KERN_ERR "%s: mutex_lock interrupted\n", __func__);
+ return -ERESTARTSYS;
+ }
+ // stopping during a start? fail
+ if (test_bit(STARTING_AID_TASK, state->flags)) {
+ printk(KERN_ERR "%s: aid task not fully started. failing op.\n", __func__);
+ rc = -EBUSY;
+ mutex_unlock(&state->dev_mutex);
+ goto err0;
+ }
+
+ // stopping during a stop? fail
+ if (test_bit(KILLING_AID_TASK, state->flags)) {
+ printk(KERN_ERR "%s: aid task being stopped. failing op.\n", __func__);
+ rc = -EBUSY;
+ mutex_unlock(&state->dev_mutex);
+ goto err0;
+ }
+
+ // task never started? fail
+ if (!state->ai_dispatch_task) {
+ printk(KERN_ERR "%s: no aid task. failing op.\n", __func__);
+ rc = -EPERM;
+ mutex_unlock(&state->dev_mutex);
+ goto err0;
+ }
+
+ // transition to quiesced state: stops accepting new requests
+ set_bit(IS_QUIESCED, state->flags);
+ // declare intent to kill ai dispatch task
+ set_bit(KILLING_AID_TASK, state->flags);
+ // exit critsec
+ mutex_unlock(&state->dev_mutex);
+
+ // kick task if asleep
+ complete(&state->aq_enq_complete);
+
+ // wait for ai dispatch task exit
+ rc = wait_for_completion_interruptible(&state->aid_exit_complete);
+ if (rc < 0) {
+ printk(KERN_ERR "%s: wait for ai dispatch task exit interrupted.\n",
+ __func__);
+ }
+
+ // critsec for manipulating flags
+ rc = mutex_lock_interruptible(&state->dev_mutex);
+ if (rc) {
+ printk(KERN_ERR "%s: mutex_lock interrupted(1)\n", __func__);
+ return -ERESTARTSYS;
+ }
+ // ai dispatch task exited (assume it did if we get interrupted): reset state
+ clear_bit(KILLING_AID_TASK, state->flags);
+ state->ai_dispatch_task = NULL;
+ // exit critsec
+ mutex_unlock(&state->dev_mutex);
+
+
+err0:
+ return rc;
+}
+
+int32_t a6_start_ai_dispatch_task(struct a6_device_state* state)
+{
+ int32_t rc = 0;
+ pid_t ai_dispatch_pid;
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: entered.\n", __func__);
+
+ // critsec for manipulating flags
+ rc = mutex_lock_interruptible(&state->dev_mutex);
+ if (rc) {
+ printk(KERN_ERR "%s: mutex_lock interrupted\n", __func__);
+ return -ERESTARTSYS;
+ }
+ // starting during a start? fail
+ if (test_bit(STARTING_AID_TASK, state->flags)) {
+ printk(KERN_ERR "%s: aid task not fully started. failing op.\n", __func__);
+ rc = -EBUSY;
+ mutex_unlock(&state->dev_mutex);
+ goto err0;
+ }
+
+ // starting during a stop? fail
+ if (test_bit(KILLING_AID_TASK, state->flags)) {
+ printk(KERN_ERR "%s: aid task being stopped. failing op.\n", __func__);
+ rc = -EBUSY;
+ mutex_unlock(&state->dev_mutex);
+ goto err0;
+ }
+
+ // task never stopped? fail
+ if (state->ai_dispatch_task) {
+ printk(KERN_ERR "%s: aid task exists. failing op.\n", __func__);
+ rc = -EPERM;
+ mutex_unlock(&state->dev_mutex);
+ goto err0;
+ }
+
+ // declare intent to start ai dispatch task
+ set_bit(STARTING_AID_TASK, state->flags);
+ // exit critsec
+ mutex_unlock(&state->dev_mutex);
+
+
+ // create ai dispatcher task...
+ ai_dispatch_pid = kernel_thread(ai_dispatch_thread_fn, state,
+ CLONE_KERNEL);
+ if (ai_dispatch_pid < 0) {
+ printk(KERN_ERR "%s: failed to create ai dispatcher task.\n", __func__);
+ rc = -EIO;
+ goto err0;
+ }
+
+ rc = mutex_lock_interruptible(&state->dev_mutex);
+ if (rc) {
+ printk(KERN_ERR "%s: mutex_lock interrupted(1)\n", __func__);
+ return -ERESTARTSYS;
+ }
+ // retrieve worker task struct
+ state->ai_dispatch_task = get_pid_task(find_get_pid(ai_dispatch_pid), PIDTYPE_PID);
+ ASSERT(state->ai_dispatch_task);
+
+ // transition to active state: start accepting new requests
+ clear_bit(IS_QUIESCED, state->flags);
+ // ai dispatch task started: reset state
+ clear_bit(STARTING_AID_TASK, state->flags);
+ // exit critsec
+ mutex_unlock(&state->dev_mutex);
+
+err0:
+ return rc;
+}
+#endif // A6_PQ
+
+void a6_force_wake_work_handler(struct work_struct *work)
+{
+ struct a6_device_state* state =
+ container_of(work, struct a6_device_state, a6_force_wake_work);
+ struct a6_wake_ops* wake_ops = (struct a6_wake_ops*)state->plat_data->wake_ops;
+ long diff_time;
+
+ diff_time = (long)jiffies - (long)start_last_a6_activity;
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: forcing sleep after: %ld ms\n",
+ __func__, diff_time * 1000/HZ);
+
+ start_last_a6_activity = 0;
+
+ // force A6 sleep and switch back to periodic wake...
+ // * timer may be scheduled just after we clear FORCE_WAKE_ACTIVE_BIT
+ // and hence will result in the callback being invoked with
+ // FORCE_WAKE_ACTIVE_BIT cleared. We just ignore this case...
+ mutex_lock(&state->a6_force_wake_mutex);
+ if (!test_bit(DEVICE_BUSY_BIT, state->flags)) {
+ if (test_bit(FORCE_WAKE_ACTIVE_BIT, state->flags)) {
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR,
+ "%s: disabling force_wake and enabling periodic_wake\n",
+ __func__);
+ /* force A6 sleep */
+ if (wake_ops->force_sleep) {
+ wake_ops->force_sleep(wake_ops->data);
+ }
+ /* enable periodic a6 wake (if defined) */
+ if (wake_ops->enable_periodic_wake) {
+ wake_ops->enable_periodic_wake(wake_ops->data);
+ }
+ /* now we are ready to clear FORCE_WAKE_ACTIVE_BIT */
+ clear_bit(FORCE_WAKE_ACTIVE_BIT, state->flags);
+ }
+ }
+ mutex_unlock(&state->a6_force_wake_mutex);
+}
+
+
+// timer callback used to force sleep after a force wake
+void a6_force_wake_timer_callback(ulong data)
+{
+ struct a6_device_state* state = (struct a6_device_state*)data;
+ int32_t rc;
+
+ rc = queue_work(state->ka6d_fw_workqueue, &state->a6_force_wake_work);
+ if (!rc) {
+ printk(KERN_ERR "**** %s: failed queueing force_wake work item.\n", __func__);
+ }
+}
+
+static int a6_pmem_open(struct inode *inode, struct file *file)
+{
+ struct a6_device_state* state;
+
+ /* get device */
+ state = container_of(file->f_op, struct a6_device_state, pmem_fops);
+
+ /* Allow only read. */
+ if ((file->f_mode & (FMODE_READ|FMODE_WRITE)) != FMODE_READ) {
+ return -EINVAL;
+ }
+
+ /* check if it is in use */
+ if (test_and_set_bit(IS_OPENED, state->flags)) {
+ return -EBUSY;
+ }
+
+ /* attach private data */
+ file->private_data = state;
+ return 0;
+}
+
+static int a6_pmem_close(struct inode *inode, struct file *file)
+{
+ struct a6_device_state* state = (struct a6_device_state*) file->private_data;
+
+ /* mark it as unused */
+ clear_bit(IS_OPENED, state->flags);
+ return 0;
+}
+
+static ssize_t a6_pmem_read(struct file *file, char __user *buf, size_t count, loff_t *ppos )
+{
+
+ ssize_t rc = 0;
+ struct a6_device_state* state;
+
+ A6_DPRINTK(A6_DEBUG_VERBOSE, KERN_ERR, "%s: enter\n", __func__);
+
+ /* input validations */
+ if (!count) {
+ return -EINVAL;
+ }
+
+ /* get state */
+ state = container_of(file->f_op, struct a6_device_state, fops);
+ rc = ttf_image_read(buf, count, ppos);
+
+ return rc;
+}
+
+struct file_operations a6_pmem_fops = {
+ .owner = THIS_MODULE,
+ .read = a6_pmem_read,
+ .open = a6_pmem_open,
+ .release = a6_pmem_close,
+};
+
+static int a6_fish_battery_get_percent(struct device *dev)
+{
+ int temp_val = 0;
+
+ a6_reg_get (dev, A6_REG_TS2_I2C_BAT_RARC, &temp_val);
+
+#if defined(CONFIG_A6_BATTERY_SCALED_MIN) && CONFIG_A6_BATTERY_SCALED_MIN != 0
+ temp_val = (temp_val - CONFIG_A6_BATTERY_SCALED_MIN) * 100
+ / (100 - CONFIG_A6_BATTERY_SCALED_MIN);
+ if (temp_val < 0) temp_val = 0;
+#endif
+
+ return temp_val;
+}
+
+static unsigned a6_calc_connected_ps(void)
+{
+ struct power_supply *psy;
+ struct a6_device_state *state;
+ unsigned int temp_val = 0;
+ unsigned connected = 0;
+
+ psy = &a6_fish_power_supplies[0];
+
+ if (!(psy->dev)) {
+ printk(KERN_ERR "%s: psy->dev is NULL\n", __func__);
+ return 0;
+ }
+ if (!(psy->dev->parent)) {
+ printk(KERN_ERR "%s: psy->dev->parent is NULL\n", __func__);
+ return 0;
+ }
+
+ state = (struct a6_device_state*)dev_get_drvdata(psy->dev->parent);
+
+ a6_reg_get (psy->dev->parent, A6_REG_TS2_I2C_FLAGS_2, &temp_val);
+
+ if (state->otg_chg_type == USB_CHG_TYPE__WALLCHARGER) {
+ connected |= MAX8903B_CONNECTED_PS_AC;
+ }
+
+ if (state->otg_chg_type == USB_CHG_TYPE__SDP
+ && (temp_val & TS2_I2C_FLAGS_2_WIRED_CHARGE)) {
+ /* NOTE: USB will not show as connected if DOCK is connected */
+ connected |= MAX8903B_CONNECTED_PS_USB;
+ }
+
+ if(temp_val & TS2_I2C_FLAGS_2_PUCK_CHARGE) {
+ connected |= MAX8903B_CONNECTED_PS_DOCK;
+ }
+
+ return connected;
+}
+
+static void a6_update_connected_ps()
+{
+ unsigned connected = a6_calc_connected_ps();
+
+ printk(KERN_INFO "%s: ac=%d usb=%d dock=%d\n", __func__,
+ (connected & MAX8903B_CONNECTED_PS_AC) ? 1 : 0,
+ (connected & MAX8903B_CONNECTED_PS_USB) ? 1 : 0,
+ (connected & MAX8903B_CONNECTED_PS_DOCK) ? 1 : 0);
+
+ max8903b_set_connected_ps(connected);
+ a6_last_ps_connect = (long)jiffies;
+}
+
+static int a6_fish_power_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ unsigned connected;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ connected = a6_calc_connected_ps();
+
+ if (
+ (psy->type == POWER_SUPPLY_TYPE_MAINS
+ && (connected & MAX8903B_CONNECTED_PS_AC)
+#ifdef CONFIG_A6_ENABLE_DOCK_PS
+ && !(connected & MAX8903B_CONNECTED_PS_DOCK)
+ && !strcmp(psy->name, "ac")
+#endif
+ )
+ ||
+ (psy->type == POWER_SUPPLY_TYPE_MAINS
+ && (connected & MAX8903B_CONNECTED_PS_DOCK)
+#ifdef CONFIG_A6_ENABLE_DOCK_PS
+ && !strcmp(psy->name, "dock")
+#endif
+ )
+ ||
+ (psy->type == POWER_SUPPLY_TYPE_USB
+ && (connected & MAX8903B_CONNECTED_PS_USB)
+ && !(connected & MAX8903B_CONNECTED_PS_DOCK)
+ )
+ ) {
+ val->intval = 1;
+ } else {
+ val->intval = 0;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+
+#define A6_BATT_STATS_DELAY (msecs_to_jiffies (15*1000))
+static int a6_fish_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int temp_val = 0;
+ unsigned connected;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (a6_fish_battery_get_percent(psy->dev->parent) == 100) {
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ } else if (a6_last_ps_connect &&
+ jiffies > a6_last_ps_connect + A6_BATT_STATS_DELAY) {
+ a6_reg_get (psy->dev->parent,
+ A6_REG_TS2_I2C_BAT_AVG_CUR_LSB_MSB, &temp_val);
+ if (temp_val > 0) {
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ } else {
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+ } else {
+ connected = a6_calc_connected_ps();
+ if (connected && !(connected == MAX8903B_CONNECTED_PS_USB)) {
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ } else {
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+ }
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+#if 0
+ /* NOTE: *_BAT_STATUS not supported by a6_reg_get yet */
+ a6_reg_get(psy->dev->parent, A6_REG_TS2_I2C_BAT_STATUS, &temp_uval);
+#endif
+ // TODO: parse temps and set OVERHEAT, parse "age" and set DEAD */
+
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = a6_fish_battery_get_percent(psy->dev->parent);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ a6_reg_get(psy->dev->parent,
+ A6_REG_TS2_I2C_BAT_CUR_LSB_MSB, &temp_val);
+ val->intval = temp_val;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ a6_reg_get (psy->dev->parent,
+ A6_REG_TS2_I2C_BAT_VOLT_LSB_MSB, &temp_val);
+ val->intval = temp_val;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ a6_reg_get (psy->dev->parent,
+ A6_REG_TS2_I2C_BAT_TEMP_LSB_MSB, &temp_val);
+ val->intval = temp_val;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ a6_reg_get (psy->dev->parent,
+ A6_REG_TS2_I2C_BAT_FULL40_LSB_MSB, &temp_val);
+ val->intval = temp_val;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ a6_reg_get (psy->dev->parent,
+ A6_REG_TS2_I2C_BAT_COULOMB_LSB_MSB, &temp_val);
+ val->intval = temp_val;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+void a6_charger_event (enum chg_type otg_chg_type)
+{
+ if (batt_state) {
+ batt_state->otg_chg_type = otg_chg_type;
+
+ power_supply_changed(&a6_fish_power_supplies[1]);
+ power_supply_changed(&a6_fish_power_supplies[2]);
+#ifdef CONFIG_A6_ENABLE_DOCK_PS
+ power_supply_changed(&a6_fish_power_supplies[3]);
+#endif
+ a6_update_connected_ps();
+ }
+ return;
+}
+EXPORT_SYMBOL (a6_charger_event);
+
+#define A6_BATT_HB_PERIOD (msecs_to_jiffies (5*60*1000))
+#define A6_INIT_CONNECTED_PS_DELAY (msecs_to_jiffies (60*1000))
+static void a6_battery_heartbeat(struct work_struct *a6_battery_work)
+{
+ int percent = 0;
+
+ struct delayed_work *temp_charge_work =
+ container_of (a6_battery_work, struct delayed_work, work);
+ struct a6_device_state* state =
+ container_of(temp_charge_work, struct a6_device_state, charge_work);
+
+ if (state->stop_heartbeat)
+ return;
+
+ if ( (percent = a6_fish_battery_get_percent(&state->i2c_dev->dev))
+ != state->last_percent){
+
+ state->last_percent = percent;
+ power_supply_changed(&a6_fish_power_supplies[0]);
+ }
+
+ if (!state->stop_heartbeat)
+ schedule_delayed_work(&state->charge_work,
+ A6_BATT_HB_PERIOD);
+}
+
+static void a6_init_connected_ps(struct work_struct *work)
+{
+ a6_update_connected_ps();
+}
+
+static int a6_fish_battery_probe(struct a6_device_state *state)
+{
+ int i;
+ int rc = 0;
+ struct i2c_client *client = state->i2c_dev;
+
+ /* init power supplier framework */
+ for (i = 0; i < ARRAY_SIZE(a6_fish_power_supplies); i++) {
+
+ rc = power_supply_register(&client->dev, &a6_fish_power_supplies[i]);
+ if (rc)
+ pr_err("%s: Failed to register power supply (%d)\n",
+ __func__, rc);
+ }
+
+ INIT_DELAYED_WORK(&state->charge_work, a6_battery_heartbeat);
+ schedule_delayed_work(&state->charge_work, A6_BATT_HB_PERIOD);
+
+ INIT_DELAYED_WORK(&state->init_connected_ps_work, a6_init_connected_ps);
+ schedule_delayed_work(&state->init_connected_ps_work, A6_INIT_CONNECTED_PS_DELAY);
+
+ return rc;
+}
+
+
+static int a6_fish_battery_remove(struct a6_device_state *state)
+{
+ int i;
+
+ state->stop_heartbeat = true;
+ cancel_delayed_work_sync(&state->charge_work);
+
+ /* init power supplier framework */
+ for (i = 0; i < ARRAY_SIZE(a6_fish_power_supplies); i++) {
+ power_supply_unregister(&a6_fish_power_supplies[i]);
+ }
+
+ return 0;
+}
+
+static int a6_fish_battery_suspend (struct a6_device_state *state)
+{
+
+ if (state->plat_data->power_supply_connected == 1 &&
+ delayed_work_pending(&state->charge_work)) {
+ state->stop_heartbeat = true;
+ smp_mb();
+ cancel_delayed_work_sync(&state->charge_work);
+ }
+
+ return 0;
+}
+
+static int a6_fish_battery_resume (struct a6_device_state *state)
+{
+ if (state->plat_data->power_supply_connected == 1) {
+ state->stop_heartbeat = false;
+ schedule_delayed_work(&state->charge_work,
+ A6_BATT_HB_PERIOD);
+
+ power_supply_changed(&a6_fish_power_supplies[0]);
+ }
+ return 0;
+}
+
+static ssize_t a6_dock_print_name(struct switch_dev *sdev, char *buf)
+{
+ bool docked = switch_get_state(sdev) != 0;
+ return sprintf(buf, docked ? "DESK\n" : "None\n");
+}
+
+static void a6_dock_update_state(struct a6_device_state *state)
+{
+ unsigned int value, dock;
+
+ if (state->dock_switch == NULL) {
+ return;
+ }
+
+ a6_reg_get (&state->i2c_dev->dev, A6_REG_TS2_I2C_FLAGS_2, &value);
+
+ if (a6_disable_dock_switch) {
+ dock = 0;
+ } else {
+ dock = value & TS2_I2C_FLAGS_2_PUCK ? 1 : 0;
+ }
+ switch_set_state(state->dock_switch, dock);
+}
+
+static int param_set_disable_dock_switch(const char *val,
+ struct kernel_param *kp)
+{
+ struct a6_device_state *state;
+
+ state = batt_state;
+
+ param_set_int(val, kp);
+ a6_dock_update_state(state);
+
+ return 0;
+}
+
+static int a6_dock_probe(struct a6_device_state *state)
+{
+ int ret;
+
+ state->dock_switch = kzalloc(sizeof(struct switch_dev), GFP_KERNEL);
+ if (state->dock_switch == NULL) {
+ return -ENOMEM;
+ }
+
+ state->dock_switch->name = "dock";
+ state->dock_switch->print_name = a6_dock_print_name;
+
+ ret = switch_dev_register(state->dock_switch);
+ if (ret < 0) {
+ kfree(state->dock_switch);
+ state->dock_switch = NULL;
+ return ret;
+ }
+
+ a6_dock_update_state(state);
+
+ return 0;
+}
+
+static void a6_dock_remove(struct a6_device_state *state)
+{
+ switch_dev_unregister(state->dock_switch);
+ kfree(state->dock_switch);
+ state->dock_switch = NULL;
+}
+
+/******************************************************************************
+* a6_i2c_probe()
+******************************************************************************/
+static int a6_i2c_probe(struct i2c_client *client, const struct i2c_device_id *dev_id)
+{
+ int rc = 0;
+ struct a6_device_state* state = NULL;
+ struct a6_platform_data* plat_data = client->dev.platform_data;
+ struct a6_wake_ops *wake_ops = (struct a6_wake_ops *) plat_data->wake_ops;
+
+ if (plat_data == NULL) {
+ /* should this be -ENODEV ? TODO */
+ return ENODEV;
+ }
+
+ state = kzalloc(sizeof(struct a6_device_state), GFP_KERNEL);
+ if(!state) {
+ /* should this be -ENOMEM ? TODO */
+ return ENOMEM;
+ }
+
+ state->a2a_rd_buf = kzalloc(A2A_RD_BUFF_SIZE, GFP_KERNEL);
+ if(!state->a2a_rd_buf) {
+ rc = -ENOMEM;
+ goto err0;
+ }
+ state->a2a_wr_buf = kzalloc(A2A_WR_BUFF_SIZE, GFP_KERNEL);
+ if(!state->a2a_wr_buf) {
+ rc = -ENOMEM;
+ goto err1;
+ }
+
+ // store i2c client device in state
+ state->i2c_dev = client;
+
+ // set platform data in device-specific driver data
+ state->plat_data = plat_data;
+
+ mutex_init(&state->dev_mutex);
+#ifdef A6_PQ
+ init_completion(&state->aq_enq_complete);
+ init_completion(&state->aid_exit_complete);
+ mutex_init(&state->aq_mutex);
+ INIT_LIST_HEAD(&state->aq_head);
+#endif // A6_PQ
+ mutex_init(&state->a6_force_wake_mutex);
+
+ // zero-init wait flags
+ bitmap_zero(state->flags, SIZE_FLAGS);
+
+ // a6 external wake enabled?
+ if (wake_ops) {
+ set_bit(CAP_PERIODIC_WAKE, state->flags);
+ }
+
+ state->ka6d_workqueue = create_workqueue("ka6d");
+ if (!state->ka6d_workqueue) {
+ printk(KERN_ERR "%s: Failed to create ka6d workqueue.\n", A6_DRIVER);
+ goto err3;
+ }
+
+ // separate wq for handling force wake timer expiry...
+ state->ka6d_fw_workqueue = create_workqueue("ka6d_fwd");
+ if (!state->ka6d_fw_workqueue) {
+ printk(KERN_ERR "%s: Failed to create ka6d_fwd workqueue.\n", A6_DRIVER);
+ goto err4;
+ }
+
+ init_waitqueue_head(&state->dev_busyq);
+
+ INIT_WORK(&state->a6_irq_work, a6_irq_work_handler);
+ if (test_bit(CAP_PERIODIC_WAKE, state->flags)) {
+ INIT_WORK(&state->a6_force_wake_work, a6_force_wake_work_handler);
+ }
+
+ state->cpufreq_hold_flag = 0;
+
+ // set device-specific driver data
+ i2c_set_clientdata(client, state);
+
+ // configure sbw_tck
+ rc = gpio_request(plat_data->sbw_tck_gpio, "a6_sbwtck");
+ if (rc != 0) {
+ printk(KERN_ERR "%s: request failed for sbw_tck gpio, val: %d.\n",
+ A6_DRIVER, plat_data->sbw_tck_gpio);
+ goto err5;
+ }
+
+ rc = gpio_direction_output(plat_data->sbw_tck_gpio, 0);
+ if (rc != 0) {
+ printk(KERN_ERR "%s: set direction output failed for sbw_tck gpio, val: %d.\n",
+ A6_DRIVER, plat_data->sbw_tck_gpio);
+ goto err6;
+ }
+
+ // configure sbw_wkup (this is the app -> a6 wakeup used in the JTAG handshaking)
+ rc = gpio_request(plat_data->sbw_wkup_gpio, "a6_sbwwkup");
+ if (rc != 0) {
+ printk(KERN_ERR "%s: request failed for sbw_wkup gpio, val: %d.\n",
+ A6_DRIVER, plat_data->sbw_wkup_gpio);
+ goto err6;
+ }
+
+ rc = gpio_direction_output(plat_data->sbw_wkup_gpio, 0);
+ if (rc != 0) {
+ printk(KERN_ERR "%s: set direction output failed for sbw_wkup gpio, val: %d.\n",
+ A6_DRIVER, plat_data->sbw_wkup_gpio);
+ goto err7;
+ }
+
+ // configure sbw_tdio (initially configured as output and the re-configured on use)
+ rc = gpio_request(plat_data->sbw_tdio_gpio, "a6_sbwtdio");
+ if (rc != 0) {
+ printk(KERN_ERR "%s: request failed for sbw_tdio gpio, val: %d.\n",
+ A6_DRIVER, plat_data->sbw_tdio_gpio);
+ goto err7;
+ }
+
+ rc = gpio_direction_output(plat_data->sbw_tdio_gpio, 1);
+ if (rc != 0) {
+ printk(KERN_ERR "%s: set direction output failed for sbw_tdio gpio, val: %d.\n",
+ A6_DRIVER, plat_data->sbw_tdio_gpio);
+ goto err8;
+ }
+
+ // configure pwr interrupt (a6 -> app)...
+ rc = gpio_request(plat_data->pwr_gpio, "a6_pwr");
+ if (rc != 0) {
+ printk(KERN_ERR "%s: request failed for pwr gpio, val: %d., err: %d\n",
+ A6_DRIVER, plat_data->pwr_gpio, rc);
+ goto err8;
+ }
+
+ rc = request_irq(gpio_to_irq(plat_data->pwr_gpio), a6_irq,
+ IRQF_TRIGGER_FALLING,
+ "a6", state);
+ if (rc != 0) {
+ printk(KERN_ERR "%s: request irq failed for pwr_gpio: val: %d, err: %d\n", A6_DRIVER,
+ plat_data->pwr_gpio, rc);
+ goto err9;
+ }
+
+#if 0
+ /* register as misc device */
+ memcpy(&state->fops, &a6_fops, sizeof(struct file_operations));
+ state->mdev.minor = MISC_DYNAMIC_MINOR;
+ state->mdev.name = plat_data->dev_name;
+ state->mdev.fops = &state->fops;
+ rc = misc_register(&state->mdev);
+ if (rc < 0) {
+ printk(KERN_ERR "%s: Failed to register as misc device\n", A6_DRIVER);
+ goto err10;
+ }
+
+ memcpy(&state->pmem_fops, &a6_pmem_fops, sizeof(struct file_operations));
+ state->pmem_mdev.minor = MISC_DYNAMIC_MINOR;
+ snprintf(state->pmem_dev_name, sizeof(state->pmem_dev_name),
+ "%s_diag", plat_data->dev_name);
+ state->pmem_mdev.name = state->pmem_dev_name;
+ state->pmem_mdev.fops = &state->pmem_fops;
+ rc = misc_register(&state->pmem_mdev);
+ if (rc < 0) {
+ printk(KERN_ERR "%s: Failed to register as a6 pmem misc device\n", A6_DRIVER);
+ goto err11;
+ }
+
+ rc = a6_create_dev_files(state, &client->dev);
+ if (rc < 0) {
+ goto err12;
+ }
+#endif
+
+#ifdef A6_PQ
+ rc = a6_start_ai_dispatch_task(state);
+ if (rc < 0) {
+ goto err13;
+ }
+#endif // A6_PQ
+
+ // ignore errors during initialization: these may be symptomatic of missing/corrupt
+ // a6 fw which will need to be remedied via the A6_IOCTL_SET_FW_DATA ioctl to
+ // re-flash fw. so its important the driver initializes successfully to handle
+ // the request.
+ rc = a6_init_state(client);
+ if (rc < 0) {
+ printk(KERN_ERR "%s: failed to initialize, err: %d\n", A6_DRIVER, rc);
+ rc = 0;
+ }
+
+ // not needed: pending work items will "flow through" if no status changes
+ // are detected...
+ //flush_workqueue(ka6d_workqueue);
+
+#ifdef A6_PQ
+#ifdef A6_DEBUG
+ rc = a6_create_debug_interface(state);
+ if (rc < 0) {
+ printk(KERN_ERR "%s: Failed to create A6 debug interface.\n", A6_DRIVER);
+ rc = 0;
+ }
+#endif
+#endif
+
+ if (plat_data->power_supply_connected == 1){
+ if ( (rc = a6_fish_battery_probe (state)) < 0) {
+ printk(KERN_ERR "%s: Failed to register power supplies, rc: %d.\n",
+ A6_DRIVER, rc);
+ rc = 0;
+ }
+
+ batt_state = state;
+
+ if ((rc = a6_dock_probe(state)) < 0) {
+ printk(KERN_ERR "%s: Failed to register dock device, rc: %d.\n",
+ A6_DRIVER, rc);
+ rc = 0;
+ }
+ }
+
+ printk(KERN_NOTICE "A6 driver initialized successfully!\n");
+ return 0;
+
+err13:
+#if 0
+ a6_remove_dev_files(state, &client->dev);
+err12:
+ misc_register(&state->pmem_mdev);
+err11:
+ misc_unregister(&state->mdev);
+err10:
+#endif
+ free_irq(gpio_to_irq(plat_data->pwr_gpio), state);
+err9:
+ gpio_free(plat_data->pwr_gpio);
+err8:
+ gpio_free(plat_data->sbw_tdio_gpio);
+err7:
+ gpio_free(plat_data->sbw_wkup_gpio);
+err6:
+ gpio_free(plat_data->sbw_tck_gpio);
+err5:
+ destroy_workqueue(state->ka6d_fw_workqueue);
+err4:
+ destroy_workqueue(state->ka6d_workqueue);
+err3:
+ kfree(state->a2a_wr_buf);
+err1:
+ kfree(state->a2a_rd_buf);
+err0:
+ kfree(state);
+
+ return rc;
+}
+
+/******************************************************************************
+* a6_i2c_remove()
+******************************************************************************/
+static int a6_i2c_remove(struct i2c_client *client)
+{
+ struct a6_device_state* state = (struct a6_device_state*)i2c_get_clientdata(client);
+
+ if (state->plat_data->power_supply_connected == 1){
+ a6_fish_battery_remove (state);
+ }
+ if (state->dock_switch) {
+ a6_dock_remove(state);
+ }
+
+#if 0
+ a6_remove_dev_files(state, &client->dev);
+#endif
+
+ if (state->ka6d_workqueue) {
+ destroy_workqueue(state->ka6d_workqueue);
+ }
+
+ if (state->ka6d_fw_workqueue) {
+ destroy_workqueue(state->ka6d_fw_workqueue);
+ }
+
+ if (state->a2a_rd_buf) {
+ kfree(state->a2a_rd_buf);
+ }
+
+ if (state->a2a_wr_buf) {
+ kfree(state->a2a_wr_buf);
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+/******************************************************************************
+* a6_i2c_suspend
+******************************************************************************/
+static int a6_i2c_suspend(struct i2c_client *dev, pm_message_t event)
+{
+ struct a6_device_state* state = (struct a6_device_state*)i2c_get_clientdata(dev);
+
+ set_bit(IS_SUSPENDED, state->flags);
+ // configure a6 irq as wake-source to handle wake on a6 notifications
+ // (charge source detection, various threshold transgressions and emergency
+ // reset detection)...
+ if (state->plat_data->pwr_gpio_wakeup_cap) {
+ enable_irq_wake(gpio_to_irq(state->plat_data->pwr_gpio));
+ }
+
+ a6_fish_battery_suspend (state);
+
+ return 0;
+}
+
+/******************************************************************************
+* a6_i2c_resume
+******************************************************************************/
+static int a6_i2c_resume (struct i2c_client *dev)
+{
+ struct a6_device_state* state = (struct a6_device_state*)i2c_get_clientdata(dev);
+
+ // un-configure a6 irq as wake-source...
+ if (state->plat_data->pwr_gpio_wakeup_cap) {
+ disable_irq_wake(gpio_to_irq(state->plat_data->pwr_gpio));
+ }
+
+ clear_bit(IS_SUSPENDED, state->flags);
+ if (test_and_clear_bit(INT_PENDING, state->flags)) {
+ queue_work(state->ka6d_workqueue, &state->a6_irq_work);
+ }
+
+ a6_fish_battery_resume (state);
+
+ return 0;
+}
+#else
+#define a6_i2c_suspend NULL
+#define a6_i2c_resume NULL
+#endif /* CONFIG_PM */
+
+static const struct i2c_device_id a6_ids[] = {
+ {A6_DEVICE_0, },
+ {A6_DEVICE_1, },
+ {},
+};
+
+MODULE_DEVICE_TABLE(i2c, a6_ids);
+
+
+static struct i2c_driver a6_i2c_driver = {
+ .driver = {
+ .name = A6_DRIVER,
+ .owner = THIS_MODULE,
+ },
+ .id_table = a6_ids,
+ .probe = a6_i2c_probe,
+ .remove = __devexit_p(a6_i2c_remove),
+ .suspend = a6_i2c_suspend,
+ .resume = a6_i2c_resume,
+};
+
+/*********************************************************************************
+* a6_module_init(void)
+***********************************************************************************/
+static int __init a6_module_init(void)
+{
+ printk(KERN_INFO "Before a6 call to i2c_add_driver.\n");
+ return i2c_add_driver(&a6_i2c_driver);
+}
+
+/*********************************************************************************
+* cy8c24894_module_exit(void)
+***********************************************************************************/
+static void __exit a6_module_exit(void)
+{
+ i2c_del_driver(&a6_i2c_driver);
+}
+
+module_init(a6_module_init);
+module_exit(a6_module_exit);
+
+MODULE_DESCRIPTION("A6 driver");
+MODULE_LICENSE("GPL");