| /* |
| * 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"); |