diff --git a/arch/arm/configs/tenderloin_defconfig b/arch/arm/configs/tenderloin_defconfig index 8b632de6a96..e12f0e9f18c 100644 --- a/arch/arm/configs/tenderloin_defconfig +++ b/arch/arm/configs/tenderloin_defconfig @@ -1030,6 +1030,7 @@ CONFIG_PMIC8058_XOADC=y # CONFIG_PMIC8058_BATTALARM is not set CONFIG_TZCOM=y # CONFIG_QFP_FUSE is not set +CONFIG_HRES_COUNTER=y # CONFIG_C2PORT is not set # @@ -1048,6 +1049,11 @@ CONFIG_TZCOM=y # CONFIG_TI_ST is not set # CONFIG_SENSORS_LIS3_SPI is not set # CONFIG_SENSORS_LIS3_I2C is not set +CONFIG_A6_SUPPORT=y +CONFIG_A6=y +# CONFIG_A6_I2C_SINGLE_BYTE is not set +# CONFIG_A6_I2C_SINGLE_BYTE_WRITE is not set +CONFIG_A6_BATTERY=y CONFIG_HAVE_IDE=y # CONFIG_IDE is not set diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 6bb50733769..33608eb88e9 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -651,6 +651,11 @@ config QFP_FUSE to the fuse block. Currently this is supported only on FSM targets. +config HRES_COUNTER + tristate "High resolution counter" + ---help--- + Select Y if you want to enable High resolution counter. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" @@ -658,5 +663,6 @@ source "drivers/misc/iwmc3200top/Kconfig" source "drivers/misc/ti-st/Kconfig" source "drivers/misc/lis3lv02d/Kconfig" source "drivers/misc/carma/Kconfig" +source "drivers/misc/a6/Kconfig" endif # MISC_DEVICES diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index cd79a060557..5abe5ddaff3 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -68,3 +68,5 @@ obj-$(CONFIG_QFP_FUSE) += qfp_fuse.o obj-$(CONFIG_KERNEL_LOG) += klog.o obj-$(CONFIG_HSUART) += hsuart.o obj-$(CONFIG_USER_PINS) += user-pins.o +obj-$(CONFIG_HRES_COUNTER) += hres_counter.o +obj-$(CONFIG_A6) += a6/ diff --git a/drivers/misc/a6/Kconfig b/drivers/misc/a6/Kconfig new file mode 100644 index 00000000000..6fd064b2ea2 --- /dev/null +++ b/drivers/misc/a6/Kconfig @@ -0,0 +1,40 @@ +menuconfig A6_SUPPORT + tristate "Palm A6 support" + depends on I2C + default y + help + This option enables support for Palm A6 controller. + + To compile this driver as a module, choose M here. + +if A6_SUPPORT + +config A6 + tristate "Palm A6 charging controller" + depends on I2C + default y + help + Say Y to include support for the Palm A6 charging controller. + +config A6_I2C_SINGLE_BYTE + tristate "Select A6 i2c single byte read" + depends on I2C && A6 + default n + help + Say Y to enable single byte i2c read for A6 driver. + +config A6_I2C_SINGLE_BYTE_WRITE + tristate "Select A6 i2c single byte write" + depends on I2C && A6 + default n + help + Say Y to enable single byte i2c write for A6 driver. + +config A6_BATTERY + bool "Palm A6 battery driver" + depends on A6 + default y + help + Say Y to include support for the Palm A6 battery. + +endif # POWER_SUPPLY diff --git a/drivers/misc/a6/Makefile b/drivers/misc/a6/Makefile new file mode 100644 index 00000000000..63df8739f44 --- /dev/null +++ b/drivers/misc/a6/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_A6) += a6.o +obj-$(CONFIG_A6) += high_level_funcs.o +obj-$(CONFIG_A6) += low_level_funcs.o +obj-$(CONFIG_A6) += jtag_funcs.o +obj-$(CONFIG_A6_BATTERY) += a6_battery.o diff --git a/drivers/misc/a6/a6.c b/drivers/misc/a6/a6.c new file mode 100644 index 00000000000..8f161545493 --- /dev/null +++ b/drivers/misc/a6/a6.c @@ -0,0 +1,4268 @@ +/* + * Includes + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_HIGH_RES_TIMERS +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "high_level_funcs.h" +#include +#include + +#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; + +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 + ); + +#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 + +/* 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 0x0201 + + +/* page 0x03 */ +/* log */ +#define TS2_I2C_LOG_LEVEL 0x0300 +#define TS2_I2C_LOG_INT_THRESHOLD 0x0301 +#define TS2_I2C_LOG_LOST_MSB 0x0302 +#define TS2_I2C_LOG_LOST_LSB 0x0303 +#define TS2_I2C_LOG_COUNT_MSB 0x0304 +#define TS2_I2C_LOG_COUNT_LSB 0x0305 +#define TS2_I2C_LOG_ENTRY_MSB 0x0306 +#define TS2_I2C_LOG_ENTRY_LSB 0x0307 + + +/* 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_HW_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_PRODUCT_TYPE_HI 0x0505 +#define TS2_I2C_ENUM_REMOTE_PRODUCT_TYPE_LO 0x0506 +#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_HW_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 RSENSE_DEFAULT 20; +#define FORCE_WAKE_TIMER_EXPIRY (HZ/20) + +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 + 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; +}; + +#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); + +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); + +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_HW_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_HW_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}, +}; + + +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}, +}; + +#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 + do { + rc = wait_event_interruptible(ai->ai_waitq, ai->ai_complete); + if (rc < 0) { + printk(KERN_ERR "%s: wait on action_item complete interrupted\n", __func__); + } + } while(rc < 0); + + 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_interruptible(&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); + 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_interruptible(&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); + udelay(700); +#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); + } + } + } + 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); + udelay(700); +#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; +} + +static ssize_t a6_generic_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + int32_t ret = 0, i = 0; + uint8_t 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 3 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)); + ret = a6_i2c_read_reg(client, reg_desc->id, reg_desc->num_ids, vals); + if (ret < 0) { + goto err0; + } + + 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 + +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; +} + +// 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 3 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; + struct a6_device_state* state = i2c_get_clientdata(client); + uint16_t cksum1, cksum2, cksum_cycles; + 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 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__); + 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; + } while (0); + + 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, NULL); + } + + printk(KERN_ERR "%s: cksum1: 0x%02hx; cksum2: 0x%02hx; cksum_cycles: 0x%02hx\n", + __func__, cksum1, cksum2, cksum_cycles); + + /* validate cksum */ + if (!cksum1 || !cksum2 || !cksum_cycles || (cksum1 != cksum2)) { + 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 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__); + 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) { + // 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 int a6_ioctl(struct inode * inode, 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); + } + 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; + + 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; + 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); + *state->a2a_rp = vals[0]; + 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\n", __func__, diff_time * 1000/HZ); + } + + +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; + + 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; + 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]; + rc = a6_i2c_write_reg(state->i2c_dev, reg_desc_comm_rxtx->id, + reg_desc_comm_rxtx->num_ids, state->a2a_wp); + 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: 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\n", __func__, diff_time * 1000/HZ); + } + + +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, + .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; + //int rc; + + 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__); + + 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, *reg_desc_flag2; + uint8_t vals[id_size], reg_val_status3 = 0, reg_val_status2 = 0, reg_val_flag2 = 0; + 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); + } + + // 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]; + reg_desc_flag2 = &a6_register_desc_arr[31]; + + /* (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]; + + /* (2) read flag2 */ + memset(vals, 0, sizeof(vals)); + ret = a6_i2c_read_reg(state->i2c_dev, reg_desc_flag2->id, reg_desc_flag2->num_ids, vals); + if (ret < 0) { + printk(KERN_ERR "%s: error reading reg: %s, id: 0x%x\n", + __func__, reg_desc_flag2->debug_name, reg_desc_flag2->id[0]); + goto err0; + } + reg_val_flag2 = 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__); + printk(KERN_ERR"%s: charger-source change detected, reg_val_flag2= %x\n", + __func__, reg_val_flag2); + if ((reg_val_flag2 & (1 << 3)) && !(reg_val_flag2 & (1 << 4))) { + max8903_charger_connected(1, POWER_SUPPLY_TYPE_MAINS); + max8903_charger_draw_current(1400); + } else { + if (!(reg_val_flag2 & (1 << 3)) && !(reg_val_flag2 & (1 << 4))) { + max8903_charger_connected(0, POWER_SUPPLY_TYPE_MAINS); + } + } + /* 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]); + } + + /* 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 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 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 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]); + } + } + +err0: + clear_bit(DEVICE_BUSY_BIT, state->flags); + 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 before we clear FORCE_WAKE_ACTIVE_BIT and + // hence will result in the callback being invoked with + // FORCE_WAKE_ACTIVE_BIT not 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, +}; + + + +#if defined(CONFIG_A6_BATTERY) +void a6_battery_init(struct i2c_client *a6_i2c_client); +#endif +/****************************************************************************** +* a6_i2c_probe() +******************************************************************************/ +static int a6_i2c_probe(struct i2c_client *client, const struct i2c_device_id *dev_id) +{ + int32_t rc; + 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) { + rc = (-ENODEV); + goto err0; + } + + state = kzalloc(sizeof(struct a6_device_state), GFP_KERNEL); + if(!state) { + rc = (-ENOMEM); + goto err0; + } + + 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 err0; + } + + // 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 err0; + } + + // 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 err0; + } + + 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 err1; + } + + 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 err1; + } + + // 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 err2; + } + + 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 err2; + } + + // 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 err3; + } + + 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 err3; + } + + // 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 err4; + } + + 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 err5; + } + + /* 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 err6; + } + + 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 err6; + } + + rc = a6_create_dev_files(state, &client->dev); + if (rc < 0) { + goto err6; + } + +#ifdef A6_PQ + rc = a6_start_ai_dispatch_task(state); + if (rc < 0) { + goto err7; + } +#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 defined(CONFIG_A6_BATTERY) + if (!strcmp(client->name, A6_DEVICE_0) ) { + + a6_battery_init(client); + } +#endif + printk(KERN_NOTICE "A6 driver initialized successfully!\n"); + return 0; + +err7: + a6_remove_dev_files(state, &client->dev); +err6: + free_irq(gpio_to_irq(plat_data->pwr_gpio), state); +err5: + gpio_free(plat_data->pwr_gpio); +err4: + gpio_free(plat_data->sbw_tdio_gpio); +err3: + gpio_free(plat_data->sbw_wkup_gpio); +err2: + gpio_free(plat_data->sbw_tck_gpio); +err1: + kfree(state); +err0: + kfree(state); + return -ENODEV; +} + +/****************************************************************************** +* 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); + + a6_remove_dev_files(state, &client->dev); + + if (state->ka6d_workqueue) { + destroy_workqueue(state->ka6d_workqueue); + } + + if (state->ka6d_fw_workqueue) { + destroy_workqueue(state->ka6d_fw_workqueue); + } + + 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); + + // configure a6 irq as wake-source to handle wake on a6 notifications + // (charge source detection, various threshold transgressions and emergency + // reset detection)... + enable_irq_wake(gpio_to_irq(state->plat_data->pwr_gpio)); + + 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... + disable_irq_wake(gpio_to_irq(state->plat_data->pwr_gpio)); + + 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"); diff --git a/drivers/misc/a6/a6_battery.c b/drivers/misc/a6/a6_battery.c new file mode 100644 index 00000000000..df0d573f2aa --- /dev/null +++ b/drivers/misc/a6/a6_battery.c @@ -0,0 +1,832 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_HIGH_RES_TIMERS +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "high_level_funcs.h" +#include + +#include +#include +#ifdef CONFIG_HAS_EARLYSUSPEND +#include +#include +#endif + +/* 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_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)) + +#if defined(current) +#undef current +#endif +struct a6_battery_info { + struct i2c_client *client; + struct power_supply battery; + struct delayed_work poll_work; + struct workqueue_struct *poll_workqueue; +#ifdef CONFIG_HAS_EARLYSUSPEND + suspend_state_t pm_state; + struct early_suspend early_suspend; +#endif + int present; + int authentic; + int percentage; + int voltage; + int current; + int temperature; + unsigned int capacity; /* in uAh */ +}; +static struct a6_battery_info battery_info; +typedef enum { + A6_ERR_OK = 0, + A6_ERROR = -1, + A6_ERR_I2C = -2, + A6_ERR_TIMEOUT = -3, + A6_ERR_NOT_SUPPORTED = -4, + A6_ERR_BUSY = -5, +} a6_err_code; + +typedef enum { + A6_CMD_REG_READ , + A6_CMD_REG_WRITE, +} a6_reg_cmd_code; + +enum { + DEVICE_BUSY_BIT = 0, + IS_OPENED, + IS_INITIALIZED_BIT, + BOOTLOAD_ACTIVE_BIT, + FORCE_WAKE_ACTIVE_BIT, + + // capabilities + CAP_PERIODIC_WAKE, +#ifdef A6_PQ + STARTING_AID_TASK, + KILLING_AID_TASK, + IS_QUIESCED, +#endif + 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; + uint32_t cached_rsense_val: 16; + struct workqueue_struct* ka6d_workqueue; + struct workqueue_struct* ka6d_fw_workqueue; + + 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; +}; + + +struct a6_reg_cmd +{ + a6_reg_cmd_code cmd; + uint16_t reg; + uint8_t data; +}; + +#define CHALLENGE_SIZE 8 +#define MAC_SIZE 20 + +#define R_SENSE r_sense + +#define SIGN_EXTEND16(x) ((x)-(((x)&0x8000)?65536:0)) +#define CURRENT_VALUE(x) ((SIGN_EXTEND16(x)*3125)/2/R_SENSE) // in uA +#define VOLTAGE_VALUE(x) (4880*((x)>>5)) // in micro volt +#define COULOMB_VALUE(x) ((6250*(x))/R_SENSE) +#define REG_COULOMB_VALUE(x) ((R_SENSE*(x))/6250) +#define CAPACITY_VALUE(x) (1600*(x)) // in micro Ahr +#define CAPACITY_PERCENT(x) (392*x) // in thousands of % + +#define BATTERY_DEFAULT_CHALLENGE \ + { 0x74, 0xca, 0x85, 0x99, 0x19, 0xde, 0xd1, 0xb3 } +#define BATTERY_DEFAULT_RESPONSE \ + { 0x87, 0xed, 0x20, 0x89, 0xad, 0x68, 0xa2, 0xcd, 0x6f, 0x93, \ + 0x13, 0x03, 0x07, 0x5a, 0x29, 0x85, 0xdc, 0x2e, 0xe9, 0x50 } + +static int battery_authentic = 0; +static int r_sense = 20; + +#define A6_BATTERY_POLLING_DELAY_MS (10) +#define A6_BATTERY_POLLING_TIMEOUT_CNT (20) +void a6_delay(uint32_t delay_ms){ + msleep(delay_ms); +} +int32_t a6_i2c_read_reg(struct i2c_client* client, const uint16_t* ids, uint32_t num_ids, uint8_t* out); +int32_t a6_i2c_write_reg(struct i2c_client* client, const uint16_t* ids, uint32_t num_ids, const uint8_t* in); + + +a6_err_code a6_send_reg_commands(struct a6_reg_cmd *cmdlist, int count) +{ + a6_err_code res = A6_ERR_OK; + int i; + struct a6_reg_cmd *p_cmd = cmdlist; + struct a6_device_state* state = i2c_get_clientdata(battery_info.client); + + /* return if a6 un-initialized */ + if (!test_bit(IS_INITIALIZED_BIT, state->flags)) { + pr_debug("%s: a6 un-initialized: exiting.\n", __func__); + res = A6_ERROR; + return res; + } + /* loop through to send all commands */ + for (i = 0; i < count; i++) { + int i2c_res; + + if (p_cmd->cmd == A6_CMD_REG_READ ) { + + i2c_res = a6_i2c_read_reg(battery_info.client, &(p_cmd->reg), 1, &(p_cmd->data)); + + if (i2c_res < 0) { + res = A6_ERR_I2C; + } + } else if (p_cmd->cmd == A6_CMD_REG_WRITE) { + i2c_res = a6_i2c_write_reg(battery_info.client, &(p_cmd->reg), 1, &(p_cmd->data)); + + if (i2c_res < 0) { + res = A6_ERR_I2C; + } + } else { + printk("A6 a6_send_reg_commands : Unknown Cmd %x\n", p_cmd->cmd); + res = A6_ERROR; + } + + //Workaround for A6 I2C read issue + //a6_delay(1); + + p_cmd++; + } + return res; +} +static a6_err_code a6_battery_wait_busy(uint8_t clear_flags, uint8_t set_flags, uint8_t *p_status) { + int i; + a6_err_code res = A6_ERR_OK; + uint8_t status = 0; + static struct a6_reg_cmd cmdlist[] = { + { + .cmd = A6_CMD_REG_READ, + .reg = TS2_I2C_BAT_COMMAND_STATUS, + }, + }; + + for ( i = A6_BATTERY_POLLING_TIMEOUT_CNT; i > 0; i-- ) { + res = a6_send_reg_commands(cmdlist, 1); + + if (res != A6_ERR_OK) { + goto a6_battery_wait_busy_exit; + } else { + status = cmdlist[0].data; + } + + if ( ( !set_flags || (status & set_flags) != 0) && ( !clear_flags || ((~status) & clear_flags) != 0 )) { + break; + } + + a6_delay(A6_BATTERY_POLLING_DELAY_MS); + }; + + if ( i <= 0 ) { + res = A6_ERROR; + } else if ( p_status ) { + *p_status = status; + } +a6_battery_wait_busy_exit: + return res; +} +//:TODO: Add busy handling - probably return the last value +//:TODO: Add A6 capabilites handling + +int battery_get_percentage(void) +{ + ushort percentage = 0; + + static struct a6_reg_cmd cmdlist[] = { + { + .cmd = A6_CMD_REG_READ, + .reg = TS2_I2C_BAT_RARC, + }, + }; + + if ( A6_ERR_OK != a6_battery_wait_busy(0, TS2_I2C_BAT_STATUS_REGS_VALID, NULL)){ + goto battery_get_percentage_exit; + } + + if (A6_ERR_OK != a6_send_reg_commands(cmdlist, 1)) { + goto battery_get_percentage_exit; + } else { + percentage = cmdlist[0].data; + } + +battery_get_percentage_exit: + return percentage; +} + +int battery_get_voltage(void) +{ + int res = 0; + ushort voltage = 0; + + static struct a6_reg_cmd cmdlist[] = { + { + .cmd = A6_CMD_REG_READ, + .reg = TS2_I2C_BAT_VOLT_MSB, + }, + { + .cmd = A6_CMD_REG_READ, + .reg = TS2_I2C_BAT_VOLT_LSB, + }, + }; + + if ( A6_ERR_OK != a6_battery_wait_busy(0, TS2_I2C_BAT_STATUS_REGS_VALID, NULL)){ + goto battery_get_voltage_exit; + } + + if (A6_ERR_OK != a6_send_reg_commands(cmdlist, 2)) { + goto battery_get_voltage_exit; + } else { + voltage = (cmdlist[0].data << 8) | cmdlist[1].data; + res = VOLTAGE_VALUE(voltage); + } + +battery_get_voltage_exit : + return res; +} + +static int battery_get_r_sense(void) +{ + int res = 0; + + static struct a6_reg_cmd cmdlist[] = { + { + .cmd = A6_CMD_REG_READ, + .reg = TS2_I2C_BAT_RSNSP, + }, + }; + + if ( A6_ERR_OK != a6_battery_wait_busy(0, TS2_I2C_BAT_STATUS_REGS_VALID, NULL)){ + goto battery_get_r_sense_exit; + } + + if (A6_ERR_OK != a6_send_reg_commands(cmdlist, 1)) { + goto battery_get_r_sense_exit; + } else { + res = 1000 / cmdlist[0].data; + } + +battery_get_r_sense_exit : + return res; +} + +int battery_get_current(void) +{ + int res = 0; + ushort cur; + + static struct a6_reg_cmd cmdlist[] = { + { + .cmd = A6_CMD_REG_READ, + .reg = TS2_I2C_BAT_CUR_MSB, + }, + { + .cmd = A6_CMD_REG_READ, + .reg = TS2_I2C_BAT_CUR_LSB, + }, + }; + + if ( A6_ERR_OK != a6_battery_wait_busy(0, TS2_I2C_BAT_STATUS_REGS_VALID, NULL)){ + goto battery_get_current_exit; + } + + if (A6_ERR_OK != a6_send_reg_commands(cmdlist, 2)) { + goto battery_get_current_exit; + } else { + cur = (cmdlist[0].data << 8) | cmdlist[1].data; + res = CURRENT_VALUE(cur); + } + +battery_get_current_exit : + return res; +} + +/** +* @brief Return the battery temperature. +* +* Temperature value is a 11-bit two's-complement integer with a 1/8 +* degrees Centigrade resolution: +* MSB: 7 6 5 4 3 2 1 0 LSB: 7 6 5 4 3 2 1 0 +* s i i i i i i i f f f _ _ _ _ _ +* +* s: sign +* i: integer part +* f: fraction part +* _: N/A +* +* We discard the fraction and only return the signed integer value. +*/ +int battery_get_temperature(void) +{ + int res = 0; + static struct a6_reg_cmd cmdlist[] = { + { + .cmd = A6_CMD_REG_READ, + .reg = TS2_I2C_BAT_TEMP_MSB, + }, + { + .cmd = A6_CMD_REG_READ, + .reg = TS2_I2C_BAT_TEMP_LSB, + }, + }; + + if ( A6_ERR_OK != a6_battery_wait_busy(0, TS2_I2C_BAT_STATUS_REGS_VALID, NULL)){ + goto battery_get_temperature_exit; + } + + if (A6_ERR_OK != a6_send_reg_commands(cmdlist, 2)) { + goto battery_get_temperature_exit; + } else { + /* Only return the signed integer value */ + res = (int8_t)cmdlist[0].data; + } + +battery_get_temperature_exit: + return res; +} + +unsigned int battery_get_capacity(void) +{ + unsigned int res = 0; + + static struct a6_reg_cmd cmdlist[] = { + { + .cmd = A6_CMD_REG_READ, + .reg = TS2_I2C_BAT_RAAC_MSB, + }, + { + .cmd = A6_CMD_REG_READ, + .reg = TS2_I2C_BAT_RAAC_LSB, + }, + }; + + if ( A6_ERR_OK != a6_battery_wait_busy(0, TS2_I2C_BAT_STATUS_REGS_VALID, NULL)){ + goto battery_get_capacity_exit; + } + + if (A6_ERR_OK != a6_send_reg_commands(cmdlist, 2)) { + goto battery_get_capacity_exit; + } else { + uint32_t capacity = (cmdlist[0].data << 8) | cmdlist[1].data; + res = CAPACITY_VALUE(capacity); + } + +battery_get_capacity_exit: + + return res; +} + +int battery_is_present(void) +{ + int rc = 0; + if (battery_get_voltage() == 0 && battery_get_current() == 0) + { + // We do not have a battery, or best case the battery we have is unusable + rc = -1; + } + return (rc < 0) ? 0 : 1; +} + +int battery_reauthenticate(void) +{ + int res = 0; + int i; + uint8_t batt_cmd_status = 0; + static int cmd_lists_initiated = 0; + static const uint8_t dc[] = BATTERY_DEFAULT_CHALLENGE; + static const uint8_t dr[] = BATTERY_DEFAULT_RESPONSE; + static struct a6_reg_cmd send_challenge_cmdlist[CHALLENGE_SIZE]; + static struct a6_reg_cmd get_response_cmdlist[MAC_SIZE]; + static struct a6_reg_cmd authenticate_cmdlist[] = { + { + .cmd = A6_CMD_REG_WRITE, + .reg = TS2_I2C_BAT_COMMAND_STATUS, + .data = TS2_I2C_BAT_COMMAND_AUTH, + }, + }; + + printk("Starting battery authentication...\n"); + + if ( !cmd_lists_initiated ) { + + //Init the challenge a6 cmd list + for ( i = 0; i < CHALLENGE_SIZE; i ++ ) { + send_challenge_cmdlist[i].cmd = A6_CMD_REG_WRITE; + send_challenge_cmdlist[i].reg = TS2_I2C_BAT_CHALLENGE(i); + send_challenge_cmdlist[i].data = dc[i]; + } + + //Init the response a6 cmd list + for ( i = 0; i < MAC_SIZE; i ++ ) { + get_response_cmdlist[i].cmd = A6_CMD_REG_READ; + get_response_cmdlist[i].reg = TS2_I2C_BAT_RESPONSE(i); + } + + cmd_lists_initiated = 1; + } + + if ( A6_ERR_OK != a6_battery_wait_busy(TS2_I2C_BAT_STATUS_BUSY, 0, NULL)){ + goto battery_reauthenticate_exit; + } + + if (A6_ERR_OK != a6_send_reg_commands(send_challenge_cmdlist, CHALLENGE_SIZE)) { + goto battery_reauthenticate_exit; + } + + if (A6_ERR_OK != a6_send_reg_commands(authenticate_cmdlist, 1)) { + goto battery_reauthenticate_exit; + } + + a6_delay(10); + + if ( A6_ERR_OK != a6_battery_wait_busy(TS2_I2C_BAT_STATUS_BUSY, 0, &batt_cmd_status)){ + goto battery_reauthenticate_exit; + } + + if ( batt_cmd_status & TS2_I2C_BAT_STATUS_AUTH_FAIL ) { + goto battery_reauthenticate_exit; + } + + if (A6_ERR_OK != a6_send_reg_commands(get_response_cmdlist, MAC_SIZE)) { + goto battery_reauthenticate_exit; + } + + res = 1; + //Validate response + for ( i = 0; i < MAC_SIZE; i++) { + if ( dr[i] != get_response_cmdlist[i].data ) { + res = 0; + break; + } + } + + if (0 == res) { + printk("*** Battery Authentication Failure: ***\n"); + printk("*** [Expected and actual response dump] ***\n"); + for ( i = 0; i < MAC_SIZE; i++) { + printk("exp-resp: 0x%02x act-resp: 0x%02x\n", dr[i], get_response_cmdlist[i].data); + } + } else { + printk("Battery authentication successful.\n"); + } + +battery_reauthenticate_exit : + + battery_authentic = res; + return res; +} + +int battery_set_authentic(bool authentic) +{ + battery_authentic = authentic; + return battery_authentic; +} + +static a6_err_code a6_battery_send_command(uint8_t command) +{ + a6_err_code res = A6_ERR_OK; + + res = a6_battery_wait_busy(TS2_I2C_BAT_STATUS_BUSY, 0, NULL); + + if ( A6_ERR_OK == res ){ + struct a6_reg_cmd cmdlist[] = { + { + .cmd = A6_CMD_REG_WRITE, + .reg = TS2_I2C_BAT_COMMAND_STATUS, + .data = command, + } + }; + + res = a6_send_reg_commands(cmdlist, 1); + } + + return res; +} + +static a6_err_code battery_refresh_regs(void) +{ + a6_err_code res = A6_ERR_OK; + + res = a6_battery_send_command(TS2_I2C_BAT_COMMAND_REFRESH); + + if ( res == A6_ERR_OK ) { + msleep(20); + } + + return res; +} + +void battery_startup(void) +{ + if ( a6_battery_send_command(TS2_I2C_BAT_COMMAND_WAKE) == A6_ERR_OK ) { + //Wait for 200 ms for battery to be woken up + msleep(200); + + //Refresh all the registers + if ( battery_refresh_regs() == A6_ERR_OK ) { + int r_sense_tmp; + + r_sense_tmp = battery_get_r_sense(); + + if ( r_sense_tmp ) { + r_sense = r_sense_tmp; + } + } + } +} + +void battery_sleep(void) +{ + a6_battery_send_command(TS2_I2C_BAT_COMMAND_OFF); +} + +int battery_read(struct a6_battery_info *batt_info) +{ + battery_refresh_regs(); + + batt_info->present = battery_is_present(); + if (!batt_info->present) { + return 0; + } + + batt_info->authentic = battery_authentic; /* read once on init */ + batt_info->percentage = battery_get_percentage(); + batt_info->voltage = battery_get_voltage(); + batt_info->current = battery_get_current(); + batt_info->temperature = battery_get_temperature(); + batt_info->capacity = battery_get_capacity(); + + return 0; +} + +static enum power_supply_property a6_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_PRESENT, +}; +static void a6_battery_ext_power_changed(struct power_supply *psy) +{ + //battery_read(&battery_info); + //pr_debug("%s(%d) : battery_info.percentage = %d, battery_info.voltage = %d, battery_info.current = %d, battery_info.temperature = %d\n", + // __func__, __LINE__, battery_info.percentage, battery_info.voltage, battery_info.current, battery_info.temperature); + //power_supply_changed(psy); + + pr_debug("%s(%d): battery_info.pm_state = %d", __func__, __LINE__, battery_info.pm_state); + if (battery_info.pm_state < PM_SUSPEND_MEM) { + if (delayed_work_pending(&(battery_info.poll_work))) { + cancel_delayed_work_sync(&(battery_info.poll_work)); + } + queue_delayed_work(battery_info.poll_workqueue , &(battery_info.poll_work), msecs_to_jiffies(10)); + } +} +static int a6_battery_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + + int status, ret = 0; + bool charger_is_charging = false, charger_is_connecting = false; + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + charger_is_charging = max8903_charger_is_charging(); + charger_is_connecting = max8903_charger_is_connecting(); + + //status + if (!battery_info.present) { + status = POWER_SUPPLY_STATUS_UNKNOWN; + } else { + if ((battery_info.percentage== 100) && charger_is_connecting && !charger_is_charging) + status = POWER_SUPPLY_STATUS_FULL; + else if (charger_is_connecting && !charger_is_charging) + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + else if (!charger_is_connecting) + status = POWER_SUPPLY_STATUS_DISCHARGING; + else + status = POWER_SUPPLY_STATUS_CHARGING; + } + pr_debug("A6_BATTEERY : (%s) (%d) : prop = %d, status = %d, percentage = %d, charger_is_connecting = %d, charger_is_charging = %d\n", + __func__, __LINE__, prop, status, battery_info.percentage, charger_is_connecting, charger_is_charging); + val->intval = status; + break; + + case POWER_SUPPLY_PROP_CAPACITY: + //capacity + pr_debug("A6_BATTEERY : (%s) (%d) : prop = %d, capacity = %d\n", __func__, __LINE__, prop, battery_info.percentage); + val->intval = battery_info.percentage; + break; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + pr_debug("A6_BATTEERY : (%s) (%d) : prop = %d, voltage_mV = %d\n", __func__, __LINE__, prop, battery_info.voltage); + val->intval = battery_info.voltage; + break; + + case POWER_SUPPLY_PROP_TEMP: + //temp + pr_debug("A6_BATTEERY : (%s) (%d) : prop = %d, temp_c = %d\n", __func__, __LINE__, prop, battery_info.temperature); + val->intval = battery_info.temperature * 10; + break; + + case POWER_SUPPLY_PROP_HEALTH: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LIPO; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = battery_info.present; + break; + default: + ret = -EINVAL; + } + return ret; +} +void a6_battery_poll_work_handler(struct work_struct *work) +{ + battery_read(&battery_info); + pr_debug("%s(%d) : battery_info.percentage = %d, battery_info.voltage = %d, battery_info.current = %d, battery_info.temperature = %d\n", + __func__, __LINE__, battery_info.percentage, battery_info.voltage, battery_info.current, battery_info.temperature); + power_supply_changed(&(battery_info.battery)); + queue_delayed_work(battery_info.poll_workqueue , &(battery_info.poll_work), msecs_to_jiffies(10 * 1000)); +} +#ifdef CONFIG_HAS_EARLYSUSPEND +void batt_early_suspend(struct early_suspend *h) +{ + pr_debug("%s(%d): enter\n", __func__, __LINE__); + + if (delayed_work_pending(&(battery_info.poll_work))) { + cancel_delayed_work_sync(&(battery_info.poll_work)); + } + battery_info.pm_state = PM_SUSPEND_MEM; + pr_debug("%s: exit\n", __func__); +} + +void batt_late_resume(struct early_suspend *h) +{ + pr_debug("%s(%d): enter\n", __func__, __LINE__); + if (delayed_work_pending(&(battery_info.poll_work))) { + cancel_delayed_work_sync(&(battery_info.poll_work)); + } + queue_delayed_work(battery_info.poll_workqueue , &(battery_info.poll_work), msecs_to_jiffies(1)); + battery_info.pm_state = PM_SUSPEND_ON; + pr_debug("%s(%d): exit\n", __func__, __LINE__); +} + +#endif +void a6_battery_init(struct i2c_client *a6_i2c_client) +{ + battery_info.battery.name = "battery"; + battery_info.battery.type = POWER_SUPPLY_TYPE_BATTERY; + battery_info.battery.properties = a6_battery_props; + battery_info.battery.num_properties = ARRAY_SIZE(a6_battery_props); + battery_info.battery.get_property = a6_battery_get_property; + battery_info.battery.external_power_changed = a6_battery_ext_power_changed; + battery_info.client = a6_i2c_client; +#ifdef CONFIG_HAS_EARLYSUSPEND + battery_info.pm_state = PM_SUSPEND_ON; + battery_info.early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN; + battery_info.early_suspend.suspend =batt_early_suspend; + battery_info.early_suspend.resume = batt_late_resume; + register_early_suspend(&battery_info.early_suspend); +#endif + battery_startup(); + battery_reauthenticate(); + battery_read(&battery_info); + printk("battery_info.percentage = %d, battery_info.voltage = %d, battery_info.current = %d, battery_info.temperature = %d\n", + battery_info.percentage, battery_info.voltage, battery_info.current, battery_info.temperature); + battery_info.poll_workqueue = create_workqueue("a6_battery_poll_workqueue"); + INIT_DELAYED_WORK(&(battery_info.poll_work), a6_battery_poll_work_handler); + power_supply_register(&(a6_i2c_client->dev), &(battery_info.battery)); + queue_delayed_work(battery_info.poll_workqueue , &(battery_info.poll_work), msecs_to_jiffies(10 * 1000)); +} + diff --git a/drivers/misc/a6/a6_host_adapter.h b/drivers/misc/a6/a6_host_adapter.h new file mode 100644 index 00000000000..c8696d321ad --- /dev/null +++ b/drivers/misc/a6/a6_host_adapter.h @@ -0,0 +1,59 @@ +#ifndef A6_HOST_ADAPTER_H +#define A6_HOST_ADAPTER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifndef __BYTEWORD__ +#define __BYTEWORD__ +typedef unsigned short int word; +typedef unsigned char byte; +#endif + +//---------------- Should be selected desired option ------------------------ + +#define ACTIVATE_MAGIC_PATTERN 1 + + +//--------------------------------------------------------------------------- + +#ifndef __DATAFORMATS__ +#define __DATAFORMATS__ +#define F_BYTE 8 +#define F_WORD 16 +#define F_ADDR 20 +#define F_LONG 32 +#endif + +// Constants for runoff status +#define STATUS_ERROR 0 // false +#define STATUS_OK 1 // true +#define STATUS_FUSEBLOWN 2 // GetDevice returns if the security fuse is blown + +#define STATUS_ACTIVE 2 +#define STATUS_IDLE 3 + +#define nNOPS {delay(1);} //{ _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); } + + +/********/ +/* Host adapter for the sbw layer */ +/********/ +// per-target functions (separate implementation per target) +#define DisableInterrupts(flags) (a6_disable_interrupts(flags)) +#define EnableInterrupts(flags) (a6_enable_interrupts(flags)) + +#define MsDelay(milliseconds) {delay(milliseconds * 1000);} // millisecond delay loop +#define usDelay(microseconds) {delay(microseconds);} // microsecond delay loop + +#endif diff --git a/drivers/misc/a6/high_level_funcs.c b/drivers/misc/a6/high_level_funcs.c new file mode 100644 index 00000000000..cc84b51efd6 --- /dev/null +++ b/drivers/misc/a6/high_level_funcs.c @@ -0,0 +1,527 @@ +/****************************************************************************/ +/* Includes */ +/****************************************************************************/ +#include "a6_host_adapter.h" // Maps function calls to host porting-layer implementations +#include "jtag_funcs.h" // Spy-by-wire JTAG functions +#include "low_level_funcs.h" // low level user functions + +#define LOCAL_TRACE 0 + +/****************************************************************************/ +/* Global types */ +/****************************************************************************/ + +/****************************************************************************/ +/* Main section of Replicator program: User can modify/insert code as needed*/ +/****************************************************************************/ +/* + Note: All High Level JTAG Functions are applied here. +*/ + +// definition for current implementation mappings used by the sbw code... +uint16_t (*SetSBWTCK)(void) = NULL; +uint16_t (*ClrSBWTCK)(void) = NULL; +uint16_t (*SetSBWTDIO)(void) = NULL; +uint16_t (*ClrSBWTDIO)(void) = NULL; +uint16_t (*SetInSBWTDIO)(void) = NULL; +uint16_t (*SetOutSBWTDIO)(void) = NULL; +uint16_t (*GetSBWTDIO)(void) = NULL; +uint16_t (*SetSBWAKEUP)(void) = NULL; +uint16_t (*ClrSBWAKEUP)(void) = NULL; +void (*delay)(uint32_t delay_us) = NULL; +// + +typedef enum { + SBW_OK = 0, + SBW_TOK, + SBW_EOL, + SBW_EOS, + SBW_SOS, + SBW_EOI, + SBW_STATE_ERROR +} SBW_STATE_CODE; + +#define SIZEOF_NEWLINE (1) + +static int hexval(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + + return 0; +} + +SBW_STATE_CODE sbw_get_token(uint8_t* read_p, uint8_t* write_p, uint32_t* read_len_p, uint32_t* write_len_p) +{ + SBW_STATE_CODE ret; + + //assert(read_p && read_len_p && write_len_p); + + + // end-of-line + if (0x0d == *read_p && 0x0a == *(read_p+1)) { + *read_len_p = 2; + *write_len_p = 0; + ret = SBW_EOL; + + //printk("\n"); + } + // end-of-image + else if (('q' == *read_p) || ('Q' == *read_p)) { + *read_len_p = 1; + *write_len_p = 0; + ret = SBW_EOI; + + //printk("\n"); + } + else { + uint32_t val = 0; + + // section start + if ('@' == read_p[0]) { + ret = SBW_SOS; + val = hexval(read_p[1 + 0]) << 12; + val |= hexval(read_p[1 +1]) << 8; + val |= hexval(read_p[1 +2]) << 4; + val |= hexval(read_p[1 +3]); + + *read_len_p = 1+4+2; //'@' + XXXX + CRLF + *write_len_p = 2; // two bytes written + //printk("\n"); + } + // data + else { + ret = SBW_TOK; + val = hexval(read_p[0]) << 4; + val |= hexval(read_p[1]); + + // handle variation: the last 2-byte value on a line may not include trailing space + *read_len_p = 2+1; //XX + ' ' + //*read_len_p = 2 + (' ' == read_p[2]) ? 1 : 0; //XX + ' ' + *write_len_p = 1; // one byte written + } + + // no target? skip the actual write... + if (write_p) { + //*((uint32_t*)write_p) = val; + write_p[0] = val & 0x000000ff; + write_p[1] = (val >> 8) & 0x000000ff; + write_p[2] = (val >> 16) & 0x000000ff; + write_p[3] = (val >> 24) & 0x000000ff; + } + + //printk("%02x ", val); + } + + return ret; +} + + +SBW_STATE_CODE sbw_parse_line(uint8_t* read_p, uint8_t* write_p, uint32_t* read_len_p, uint32_t* write_len_p) +{ + SBW_STATE_CODE ret; + uint32_t total_read_len = 0, total_write_len = 0, val = 0, r_len = 0, w_len = 0; + + do { + ret = sbw_get_token(read_p, (uint8_t*)&val, &r_len, &w_len); + // end-of-line; break out of loop + if (SBW_EOL == ret) { + total_read_len += r_len; + *read_len_p = total_read_len; + total_write_len += w_len; + *write_len_p = total_write_len; + } + // regular token; keep looping + else if (SBW_TOK == ret) { + *((uint8_t*)write_p) = (uint8_t)val; + total_read_len += r_len; + read_p += r_len; + total_write_len += w_len; + write_p += w_len; + } + // map start-of-section/end-of-image to end-of-section + else if ((SBW_SOS == ret) || (SBW_EOI == ret)) { + *read_len_p = *write_len_p = 0; + ret = SBW_EOS; + } + // state mismatch + else { + printk("SBW_ERROR[sbw_parse_line]: wrong state returned; state: %d\n", ret); + ret = SBW_STATE_ERROR; + } + } while (SBW_TOK == ret); + + + return ret; +} + + +SBW_STATE_CODE sbw_parse_section(uint8_t* read_p, uint8_t* write_p, uint32_t* read_len_p, uint32_t* write_len_p) +{ + SBW_STATE_CODE ret; + uint32_t total_read_len = 0, total_write_len = 0, r_len = 0, w_len = 0; + + do { + ret = sbw_parse_line(read_p, write_p, &r_len, &w_len); + // end-of-section; break out of loop + if (SBW_EOS == ret){ + total_read_len += r_len; + *read_len_p = total_read_len; + total_write_len += w_len; + *write_len_p = total_write_len; + } + // end-of-line; keep looping + else if (SBW_EOL == ret) { + total_read_len += r_len; + read_p += r_len; + total_write_len += w_len; + write_p += w_len; + } + // state mismatch + else { + printk("SBW_ERROR[sbw_parse_section]: wrong state returned; state: %d\n", ret); + ret = SBW_STATE_ERROR; + } + } while (SBW_EOL == ret); + + + return ret; +} + +typedef struct { + uint32_t sec_addr[75]; + uint32_t sec_len[75]; + uint32_t num_sections; +} sec_info_struct; + +sec_info_struct sec_info; +int32_t sec_index = 0; + + +SBW_STATE_CODE sbw_parse_image(uint8_t* read_p, uint8_t* write_p, uint32_t* read_len_p, uint32_t* write_len_p) +{ + SBW_STATE_CODE ret; + uint32_t total_read_len = 0, total_write_len = 0, r_len = 0, w_len = 0, val = 0; + + memset(&sec_info, 0, sizeof(sec_info)); + + + do { + ret = sbw_get_token(read_p, (uint8_t*)&val, &r_len, &w_len); + if (SBW_SOS != ret) { + if (!sec_info.num_sections) { + printk("SBW_ERROR[sbw_parse_image]: does not start with section; value: %d\n", ret); + return SBW_STATE_ERROR; + } + + if (SBW_EOI == ret) { + *read_len_p = total_read_len; + *write_len_p = total_write_len; + ret = SBW_OK; + break; + } + else { + printk("SBW_ERROR[sbw_parse_image]: wrong state returned; state: %d\n", ret); + ret = SBW_STATE_ERROR; + break; + } + } + + //printk("[Status]: SOS detected; address: %x, r_len: %d, w_len: %d\n", val, r_len, w_len); + + total_read_len += r_len; + read_p += r_len; + + sec_info.sec_addr[sec_info.num_sections] = val; + + ret = sbw_parse_section(read_p, write_p, &r_len, &w_len); + // end-of-section; keep looping + if (SBW_EOS == ret) { + total_read_len += r_len; + read_p += r_len; + + if (w_len & 1) { + write_p[w_len] = 0xff; + w_len++; + } + total_write_len += w_len; + write_p += w_len; + + // sec_len converted to A6 words (16-bit) + sec_info.sec_len[sec_info.num_sections] = w_len/2; + sec_info.num_sections++; + } + // state mismatch + else { + printk("SBW_ERROR[sbw_parse_image:1]: wrong state returned; state: %d\n", ret); + ret = SBW_STATE_ERROR; + } + } while (SBW_EOS == ret); + + if (SBW_OK == ret) { + int idx = 0; + + printk("Parsing complete. Read size: %d, Write size: %d. Num sections: %d\n", + *read_len_p, *write_len_p, sec_info.num_sections); + while (idx < (int)sec_info.num_sections) { + printk("Section idx: %d; Addr: 0x%04x; Length: %d\n", + idx, sec_info.sec_addr[idx], sec_info.sec_len[idx]); + idx++; + } + +/* + printk("\nDumping converted data:\n"); + for (idx = 0; idx < (int)*write_len_p; idx++) { + if (!(idx % 16)) { + printk("\n"); + } + + printk("%02x ", (write_p-*write_len_p)[idx]); + } +*/ + } + + + return ret; +} + +int program_device_sbw(struct a6_sbw_interface* sbw_ops, uint32_t read_address) +{ + uint32_t read_len = 0, write_len = 0; + SBW_STATE_CODE parse_ret; + int retry = 0, ret_val = 0; + uint16_t addr; + + + if (read_address & 1) { + printk("program_fw: Please enter an even read address.\n"); + return -1; + } + + // set up the current mappings for the sbw code... + SetSBWTCK = sbw_ops->a6_per_device_interface.SetSBWTCK; + ClrSBWTCK = sbw_ops->a6_per_device_interface.ClrSBWTCK; + SetSBWTDIO = sbw_ops->a6_per_device_interface.SetSBWTDIO; + ClrSBWTDIO = sbw_ops->a6_per_device_interface.ClrSBWTDIO; + SetInSBWTDIO = sbw_ops->a6_per_device_interface.SetInSBWTDIO; + SetOutSBWTDIO = sbw_ops->a6_per_device_interface.SetOutSBWTDIO; + GetSBWTDIO = sbw_ops->a6_per_device_interface.GetSBWTDIO; + SetSBWAKEUP = sbw_ops->a6_per_device_interface.SetSBWAKEUP; + ClrSBWAKEUP = sbw_ops->a6_per_device_interface.ClrSBWAKEUP; + delay = sbw_ops->a6_per_target_interface.delay; + + parse_ret = sbw_parse_image((uint8_t*)read_address, (uint8_t*)read_address/*write_p*/, &read_len, &write_len); + if (SBW_OK != parse_ret) { + printk("Error in parsing A6 fw file...\n"); + return -1; + } + +/* TEMP: Workaround for occasional verification failure. Not root-Caused yet but, + empirically, a retry always works. Revisit.*/ +retry_0: + + InitTarget(); + + // Start of SBW access to the Target + if (GetDevice() != STATUS_OK) // Set DeviceId + { + printk("Error in GetDevice()\n"); // stop here if invalid JTAG ID or + // time-out. (error: red LED is ON) + ret_val = -1; + goto err0; + } + + + // Program the boot code + if (!WriteAllSections((const unsigned short*)read_address, (const unsigned long *)&sec_info.sec_addr[0], + (const unsigned long *)&sec_info.sec_len[0], sec_info.num_sections)) + { + printk("Error in WriteAllSections(all)\n"); + ret_val = -1; + goto err0; + } + + + if (!VerifyAllSections((const unsigned short*)read_address, (const unsigned long *)&sec_info.sec_addr[0], + (const unsigned long *)&sec_info.sec_len[0], sec_info.num_sections)) + { + printk("Error in VerifyAllSections(all)\n"); + printk("Retrying...\n\n"); + if (retry++ < 15) { + addr = ReadMem_430Xv2(F_WORD, V_RESET); + ReleaseDevice(addr, ERROR); // set PC to V_RESET contents + ReleaseTarget(); + goto retry_0; + } + else { + printk("Failure to write and verify fw file after %d retries\n", retry); + ret_val = -1; + } + } + + +err0: + + addr = ReadMem_430Xv2(F_WORD, V_RESET); + if (ReleaseDevice(addr, PROGRAM) < 0) { // set PC to V_RESET contents + printk(KERN_ERR "Checksum validation failed post-flashing.\n"); + if (retry < 15) { + printk(KERN_ERR "Retrying...\n\n"); + retry++; + ReleaseTarget(); + goto retry_0; + } + else { + printk(KERN_ERR "Failure to program fw after %d retries.\n", retry); + ret_val = -1; + } + } + + // if fail to set JTAG mode + if (ret_val == -1 && retry == 0 ) { + retry++; + ret_val = 0; + goto retry_0; + } + + ReleaseTarget(); + return ret_val; +} + + +int verify_device_sbw(struct a6_sbw_interface* sbw_ops, uint32_t read_address) +{ + uint32_t read_len = 0, write_len = 0; + SBW_STATE_CODE parse_ret; + int ret_val = 0; + uint16_t addr; + + + if (read_address & 1) { + printk("program_fw: Please enter an even read address.\n"); + return -1; + } + + // set up the current mappings for the sbw code... + SetSBWTCK = sbw_ops->a6_per_device_interface.SetSBWTCK; + ClrSBWTCK = sbw_ops->a6_per_device_interface.ClrSBWTCK; + SetSBWTDIO = sbw_ops->a6_per_device_interface.SetSBWTDIO; + ClrSBWTDIO = sbw_ops->a6_per_device_interface.ClrSBWTDIO; + SetInSBWTDIO = sbw_ops->a6_per_device_interface.SetInSBWTDIO; + SetOutSBWTDIO = sbw_ops->a6_per_device_interface.SetOutSBWTDIO; + GetSBWTDIO = sbw_ops->a6_per_device_interface.GetSBWTDIO; + SetSBWAKEUP = sbw_ops->a6_per_device_interface.SetSBWAKEUP; + ClrSBWAKEUP = sbw_ops->a6_per_device_interface.ClrSBWAKEUP; + delay = sbw_ops->a6_per_target_interface.delay; + + parse_ret = sbw_parse_image( (uint8_t*)read_address, + (uint8_t*)read_address/*write_p*/, + &read_len, &write_len); + if (SBW_OK != parse_ret) { + printk("Error in parsing A6 fw file...\n"); + return -1; + } + + InitTarget(); + + // Start of SBW access to the Target + if (GetDevice() != STATUS_OK) // Set DeviceId + { + printk("Error in GetDevice()\n"); // stop here if invalid JTAG ID or + // time-out. (error: red LED is ON) + ret_val = -1; + goto err0; + } + + if (!VerifyAllSections((const unsigned short*)read_address, + (const unsigned long *)&sec_info.sec_addr[0], + (const unsigned long *)&sec_info.sec_len[0], sec_info.num_sections)) { + printk("Error in VerifyAllSections(all)\n"); + ret_val = -1; + } + + +err0: + addr = ReadMem_430Xv2(F_WORD, V_RESET); + ReleaseDevice(addr, VERIFY); // set PC to V_RESET contents + ReleaseTarget(); + + return ret_val; +} + +int ttf_extract_fw_sbw(struct a6_sbw_interface* sbw_ops) +{ + int ret_val = 0; + uint16_t addr; + + + // set up the current mappings for the sbw code... + SetSBWTCK = sbw_ops->a6_per_device_interface.SetSBWTCK; + ClrSBWTCK = sbw_ops->a6_per_device_interface.ClrSBWTCK; + SetSBWTDIO = sbw_ops->a6_per_device_interface.SetSBWTDIO; + ClrSBWTDIO = sbw_ops->a6_per_device_interface.ClrSBWTDIO; + SetInSBWTDIO = sbw_ops->a6_per_device_interface.SetInSBWTDIO; + SetOutSBWTDIO = sbw_ops->a6_per_device_interface.SetOutSBWTDIO; + GetSBWTDIO = sbw_ops->a6_per_device_interface.GetSBWTDIO; + SetSBWAKEUP = sbw_ops->a6_per_device_interface.SetSBWAKEUP; + ClrSBWAKEUP = sbw_ops->a6_per_device_interface.ClrSBWAKEUP; + delay = sbw_ops->a6_per_target_interface.delay; + + InitTarget(); + + // Start of SBW access to the Target + if (GetDevice() != STATUS_OK) // Set DeviceId + { + printk("Error in GetDevice()\n"); // stop here if invalid JTAG ID or + // time-out. (error: red LED is ON) + ret_val = -1; + goto err0; + } + + if (!TTFExtractAllSections()) { + printk("Error in TTFExtractAllSections\n"); + ret_val = -1; + } + + +err0: + + addr = ReadMem_430Xv2(F_WORD, V_RESET); + ReleaseDevice(addr, VERIFY); // set PC to V_RESET contents + ReleaseTarget(); + return ret_val; +} + +int ttf_image_read(char *buf, size_t count, loff_t *ppos) +{ + return TTFImageRead(buf, count, ppos); +} + +int ttf_extract_cache_clear(void) +{ + TTFExtractCacheClear(); + return 0; +} + +int get_checksum_data_sbw(struct a6_sbw_interface* sbw_ops, unsigned short* cksum1, + unsigned short* cksum2, unsigned short* cksum_cycles, + unsigned short* cksum_errors) +{ + // set up the current mappings for the sbw code... + SetSBWTCK = sbw_ops->a6_per_device_interface.SetSBWTCK; + ClrSBWTCK = sbw_ops->a6_per_device_interface.ClrSBWTCK; + SetSBWTDIO = sbw_ops->a6_per_device_interface.SetSBWTDIO; + ClrSBWTDIO = sbw_ops->a6_per_device_interface.ClrSBWTDIO; + SetInSBWTDIO = sbw_ops->a6_per_device_interface.SetInSBWTDIO; + SetOutSBWTDIO = sbw_ops->a6_per_device_interface.SetOutSBWTDIO; + GetSBWTDIO = sbw_ops->a6_per_device_interface.GetSBWTDIO; + SetSBWAKEUP = sbw_ops->a6_per_device_interface.SetSBWAKEUP; + ClrSBWAKEUP = sbw_ops->a6_per_device_interface.ClrSBWAKEUP; + delay = sbw_ops->a6_per_target_interface.delay; + + return GetChecksumData(cksum1, cksum2, cksum_cycles, cksum_errors); +} diff --git a/drivers/misc/a6/high_level_funcs.h b/drivers/misc/a6/high_level_funcs.h new file mode 100644 index 00000000000..d63d7e75e57 --- /dev/null +++ b/drivers/misc/a6/high_level_funcs.h @@ -0,0 +1,13 @@ +#ifndef _high_level_funcs_h_ +#define _high_level_funcs_h_ + +int program_device_sbw(struct a6_sbw_interface* sbw_ops, uint32_t read_address); +int verify_device_sbw(struct a6_sbw_interface* sbw_ops, uint32_t read_address); +int ttf_extract_fw_sbw(struct a6_sbw_interface* sbw_ops); +int ttf_extract_cache_clear(void); +int ttf_image_read(char *buf, size_t count, loff_t *ppos); +int get_checksum_data_sbw(struct a6_sbw_interface* sbw_ops, unsigned short* cksum1, + unsigned short* cksum2, unsigned short* cksum_cycles, + unsigned short* cksum_errors); + +#endif diff --git a/drivers/misc/a6/jtag_funcs.c b/drivers/misc/a6/jtag_funcs.c new file mode 100644 index 00000000000..dc0bfc4ca82 --- /dev/null +++ b/drivers/misc/a6/jtag_funcs.c @@ -0,0 +1,1070 @@ +#include "a6_host_adapter.h" +#include "low_level_funcs.h" +#include "jtag_funcs.h" + +#define LOCAL_TRACE 1 + +// declarations for active implementation mappings used by the sbw code... +extern uint16_t (*SetSBWTCK)(void); +extern uint16_t (*ClrSBWTCK)(void); +extern uint16_t (*SetSBWTDIO)(void); +extern uint16_t (*ClrSBWTDIO)(void); +extern uint16_t (*SetInSBWTDIO)(void); +extern uint16_t (*SetOutSBWTDIO)(void); +extern uint16_t (*GetSBWTDIO)(void); +extern uint16_t (*SetSBWAKEUP)(void); +extern uint16_t (*ClrSBWAKEUP)(void); +extern void (*delay)(uint32_t delay_us); +// + +/****************************************************************************/ +/* Low level routines for accessing the target device via JTAG: */ +/****************************************************************************/ + +#define VCC_LEVEL 36 +static int8_t SetTargetVcc(int8_t level) { + + return level; +} + + +//---------------------------------------------------------------------------- +/* Function for shifting a given 16-bit word into the JTAG data register + through TDI. + Arguments: word data (16-bit data, MSB first) + Result: word (value is shifted out via TDO simultaneously) +*/ +static word DR_Shift16(word data) +{ + // JTAG FSM state = Run-Test/Idle + if (TCLK_saved) + { + TMSH_TDIH(); + } + else + { + TMSH_TDIL(); + } + // JTAG FSM state = Select DR-Scan + TMSL_TDIH(); + // JTAG FSM state = Capture-DR + TMSL_TDIH(); + // JTAG FSM state = Shift-DR, Shift in TDI (16-bit) + return(AllShifts(F_WORD, data)); + // JTAG FSM state = Run-Test/Idle +} + +//---------------------------------------------------------------------------- +/* Function for shifting a given 20-bit address word into the + JTAG address register through TDI. + Arguments: unsigned long address (20-bit address word, MSB first) + Result: unsigned long TDOvalue (is shifted out via TDO simultaneously) +*/ +static unsigned long DR_Shift20(unsigned long address) +{ + // JTAG FSM state = Run-Test/Idle + if (TCLK_saved) + { + TMSH_TDIH(); + } + else + { + TMSH_TDIL(); + } + // JTAG FSM state = Select DR-Scan + TMSL_TDIH(); + // JTAG FSM state = Capture-DR + TMSL_TDIH(); + // JTAG FSM state = Shift-DR, Shift in TDI (16-bit) + return(AllShifts(F_ADDR, address)); + // JTAG FSM state = Run-Test/Idle +} + +//---------------------------------------------------------------------------- +/* Function for shifting a new instruction into the JTAG instruction + register through TDI (MSB first, but with interchanged MSB - LSB, to + simply use the same shifting function, Shift(), as used in DR_Shift16). + Arguments: byte Instruction (8bit JTAG instruction, MSB first) + Result: word TDOword (value shifted out from TDO = JTAG ID) +*/ +static word IR_Shift(byte instruction) +{ + // JTAG FSM state = Run-Test/Idle + if (TCLK_saved) + { + TMSH_TDIH(); + } + else + { + TMSH_TDIL(); + } + // JTAG FSM state = Select DR-Scan + TMSH_TDIH(); + + // JTAG FSM state = Select IR-Scan + TMSL_TDIH(); + // JTAG FSM state = Capture-IR + TMSL_TDIH(); + // JTAG FSM state = Shift-IR, Shift in TDI (8-bit) + return(AllShifts(F_BYTE, instruction)); + // JTAG FSM state = Run-Test/Idle +} + +//---------------------------------------------------------------------------- +/* Reset target JTAG interface and perform fuse-HW check. + Arguments: None + Result: None +*/ +static void ResetTAP(void) +{ + word i; + + // Now fuse is checked, Reset JTAG FSM + for (i = 6; i > 0; i--) // 6 is nominal + { + TMSH_TDIH(); + } + // JTAG FSM is now in Test-Logic-Reset + TMSL_TDIH(); // now in Run/Test Idle +} + +//---------------------------------------------------------------------------- +/* Function to execute a Power-On Reset (POR) using JTAG CNTRL SIG register + Arguments: None + Result: word (STATUS_OK if target is in Full-Emulation-State afterwards, + STATUS_ERROR otherwise) +*/ +static word ExecutePOR_430Xv2(void) +{ + word i = 0; + + // provide one clock + ClrTCLK(); + SetTCLK(); + + // prepare access to the JTAG CNTRL SIG register + IR_Shift(IR_CNTRL_SIG_16BIT); + // release CPUSUSP signal and apply POR signal + DR_Shift16(0x0C01); + // release POR signal again + DR_Shift16(0x0401); + + // provide 5 clock cycles + for (i = 0; i < 5; i++) + { + ClrTCLK(); + SetTCLK(); + } + // now set CPUSUSP signal again + DR_Shift16(0x0501); + // and provide one more clock + ClrTCLK(); + SetTCLK(); + // the CPU is now in 'Full-Emulation-State' + + // disable Watchdog Timer on target device now by setting the HOLD signal + // in the WDT_CNTRL register + WriteMem_430Xv2(F_WORD, 0x015C, 0x5A80); + + // Check if device is again in Full-Emulation-State and return status + IR_Shift(IR_CNTRL_SIG_CAPTURE); + if(DR_Shift16(0) & 0x0301) + { + return(STATUS_OK); + } + + return(STATUS_ERROR); +} + +//---------------------------------------------------------------------------- +/* Load a given address into the target CPU's program counter (PC). + Argument: unsigned long Addr (destination address) + Result: None +*/ +static void SetPC_430Xv2(unsigned long Addr) +{ + unsigned short Mova; + unsigned short Pc_l; + + Mova = 0x0080; + Mova += (unsigned short)((Addr>>8) & 0x00000F00); + Pc_l = (unsigned short)((Addr & 0xFFFF)); + + // Check Full-Emulation-State at the beginning + IR_Shift(IR_CNTRL_SIG_CAPTURE); + if(DR_Shift16(0) & 0x0301) + { + // MOVA #imm20, PC + ClrTCLK(); + // take over bus control during clock LOW phase + IR_Shift(IR_DATA_16BIT); + SetTCLK(); + DR_Shift16(Mova); + IR_Shift(IR_CNTRL_SIG_16BIT); + DR_Shift16(0x1400); + IR_Shift(IR_DATA_16BIT); + ClrTCLK(); + SetTCLK(); + DR_Shift16(Pc_l); + ClrTCLK(); + SetTCLK(); + DR_Shift16(0x4303); + ClrTCLK(); + IR_Shift(IR_ADDR_CAPTURE); + DR_Shift20(0x00000); + SetTCLK(); + } +} + +/****************************************************************************/ +/* High level routines for accessing the target device via JTAG: */ +/* */ +/* From the following, the user is relieved from coding anything. */ +/* To provide better understanding and clearness, some functionality is */ +/* coded generously. (Code and speed optimization enhancements may */ +/* be desired) */ +/****************************************************************************/ + +#define MAX_ENTRY_TRY 4 + +static word JtagId = 0; +static word CoreId = 0; +static unsigned long DeviceIdPointer = 0; +static word DeviceId = 0; + +static void ConnectJTAG(void) +{ + /* 8051-CODE */ + // drive JTAG/TEST signals + { + DrvSignals(); + + //P2_7 = 0; + ClrSBWAKEUP(); // prepare to close sbw isolation switch + ClrSBWTCK(); + usDelay(10); + + //P2_7 = 1; + SetSBWAKEUP(); + usDelay(4); + SetSBWTCK(); + usDelay(4); + + //P2_7 = 0; + ClrSBWAKEUP(); + usDelay(4); + ClrSBWTCK(); + usDelay(4); } // <=== got left out of first email +} + + +static void StartJtag(void) +{ + + /* 8051-CODE */ + // reset TEST logic, spybiwire jtag restart + ClrSBWTCK(); + usDelay(100); + + // ensure A6 powered up + SetSBWAKEUP(); + usDelay(100); + + // ensure Rst negated + SetSBWTDIO(); + usDelay(100); + + // PHASE 1 -> TEST PIN TO 1 + SetSBWTCK(); // prepare to activate TEST logic + usDelay(100); + + // PHASE 2 -> TEST PIN TO 0 + ClrSBWTCK(); // issue phantom spybiwire clock + + // PHASE 3 + usDelay(1); // low time < 7 uSec to enalbe mcu sbw jtag + + // phase 4 -> TEST PIN TO 1 + SetSBWTCK(); + usDelay(100); + + // phase 5 + // MsDelay(5); +} + +static void StopJtag (void) +{ + /* 8051-CODE */ + // release JTAG/TEST signals + { + RlsSignals(); + //P2MDIN &= ~0x80; + //P2MDOUT &= ~0x80; + //P2_7 = 1; + SetSBWAKEUP(); + MsDelay(1); + } +} + +static word GetCoreID (void) +{ + word i; + for (i = 0; i < MAX_ENTRY_TRY; i++) + { + // initialize JtagId with an invalid value + JtagId = 0; + // release JTAG/TEST signals to savely reset the test logic + StopJtag(); + // establish the physical connection to the JTAG interface + ConnectJTAG(); + // Apply again 4wire/SBW entry Sequence. + // set ResetPin =1 + StartJtag(); + // reset TAP state machine -> Run-Test/Idle + ResetTAP(); + // shift out JTAG ID + JtagId = (word)IR_Shift(IR_CNTRL_SIG_CAPTURE); + + //printk("JTAG ID returned: %x; expected: %x\n", JtagId, JTAG_ID91); + // break if a valid JTAG ID is being returned + if(JtagId == JTAG_ID91) + break; + // + SetTargetVcc( 0 ); + MsDelay(200); + SetTargetVcc( VCC_LEVEL ); + ConnectJTAG(); + StartJtag(); + ResetTAP(); + + JtagId = (word)IR_Shift(IR_CNTRL_SIG_CAPTURE); + //printk("(2) JTAG ID returned: %x; expected: %x\n", JtagId, JTAG_ID91); + if(JtagId == JTAG_ID91) + break; + } + if(i >= MAX_ENTRY_TRY) + { + // if connected device is MSP4305438 JTAG Mailbox is not usable + #ifdef ACTIVATE_MAGIC_PATTERN + /* xxx for(i = 0; i < MAX_ENTRY_TRY; i++) + { + // if no JTAG ID is beeing returnd -> apply magic pattern to stop user cd excecution + if((JtagId = magicPattern()) == 1 || i >= MAX_ENTRY_TRY) + { + // if magic pattern faild and 4 tries passed -> return status error + return(STATUS_ERROR); + } + else + { + break; + } + }*/ + // is MSP4305438 mailbox is not usalbe + #else + return(STATUS_ERROR); + #endif + } + if(JtagId == JTAG_ID91) + { + // Get Core identification info + IR_Shift(IR_COREIP_ID); + CoreId = DR_Shift16(0); + //printk("CoreId returned: %x\n", CoreId); + if(CoreId == 0) + { + return(STATUS_ERROR); + } + IR_Shift(IR_DEVICE_ID); + DeviceIdPointer = DR_Shift20(0); + // The ID pointer is an un-scrambled 20bit value + DeviceIdPointer = ((DeviceIdPointer & 0xFFFF) << 4 ) + (DeviceIdPointer >> 16 ); + //printk("DeviceIdPointer returned: %lx\n", DeviceIdPointer); + + return(STATUS_OK); + } + else + { + return(STATUS_ERROR); + } +} + +static word SyncJtag_AssertPor (void) +{ + word i = 0; + word jid = 0; + + IR_Shift(IR_CNTRL_SIG_16BIT); + DR_Shift16(0x1501); // Set device into JTAG mode + read + jid = IR_Shift(IR_CNTRL_SIG_CAPTURE); + if (jid != JTAG_ID91) + { + printk("%s: Failed JTAGID verification. Expected: %x; ret: %x\n", __func__, JTAG_ID91, jid); + return(STATUS_ERROR); + } + // wait for sync + while(!(DR_Shift16(0) & 0x0200) && i < 50) + { + i++; + }; + // continues if sync was sucessfull + if(i >= 50) + { + return(STATUS_ERROR); + } + + // execute a Power-On-Reset + if(ExecutePOR_430Xv2() != STATUS_OK) + { + printk("%s: Failed ExecutePOR_430Xv2.\n", __func__); + return(STATUS_ERROR); + } + + return(STATUS_OK); +} +//---------------------------------------------------------------------------- +/* Function to clear and confirm watchdog disabled. + Result: word (STATUS_ERROR if unable to confirm VMON_MODE + bit 7 has been cleared) +*/ + +byte pmicWriteRead(byte addr, byte wdat) +{ + byte rdat; + + // PB(DIR|OUT) (bit 7 = sel (adr), 4 = rw (wrt), bit 0 = en) + + // spg430 control (port 3) control + WriteMem_430Xv2(F_BYTE, 0x222, 0x90 ); // set adr/wrt, clear en + WriteMem_430Xv2(F_BYTE, 0x224, 0x91 ); // enable control outputs + +// bic.b #0x01, &PBOUT_L ; clear en (0x222) +// or.b #0x90, &PBOUT_L ; set rw, sel (0x222) +// or.b #0x91, &PBDIR_L ; enable ctl (0x224) + + // spg430 data (port 2) output + WriteMem_430Xv2(F_BYTE, 0x205, 0xFF ); // enable data outputs + // F_WORD, 0x204, 0xFF00 + +// mov.b #0xff, &PADIR_H ; enable data (0x205) + + WriteMem_430Xv2(F_BYTE, 0x203, addr ); // select vmon mode reg + // F_WORD, 0x202, 0x7E00 + WriteMem_430Xv2(F_BYTE, 0x222, 0x91 ); // set en + WriteMem_430Xv2(F_BYTE, 0x222, 0x90 ); // clear en + +// mov.b #PMIC_VMON_MODE, &PAOUT_H ; (0x203) +// or.b #0x01, &PBOUT_L ; set en (0x222) +// nop +// bic.b #0x01, &PBOUT_L ; clear en (0x222) + + WriteMem_430Xv2(F_BYTE, 0x203, wdat ); // set write data + // F_WORD, 0x202, 0x0200 + WriteMem_430Xv2(F_BYTE, 0x222, 0x10 ); // clear sel + WriteMem_430Xv2(F_BYTE, 0x222, 0x11 ); // set en + WriteMem_430Xv2(F_BYTE, 0x222, 0x10 ); // clear en + +// mov.b #PMIC_SBW_EN, &PAOUT_H ; (0x203) +// bic.b #0x80, &PBOUT_L ; clear sel (0x222) +// or.b #0x01, &PBOUT_L ; set en (0x222) +// nop +// bic.b #0x01, &PBOUT_L ; clear en (0x222) + + // spg430 data (port 2) input + WriteMem_430Xv2(F_BYTE, 0x205, 0x00 ); // disable data outputs + // F_WORD, 0x204, 0x0000 + +// mov.b #0x00, &PADIR_H ; disable data (0x205) + + WriteMem_430Xv2(F_BYTE, 0x222, 0x00 ); // clear sel,rw + WriteMem_430Xv2(F_BYTE, 0x222, 0x01 ); // set en + rdat = (byte) ReadMem_430Xv2(F_BYTE, 0x201); // readback vmon mode reg + // F_WORD, 0x200 + WriteMem_430Xv2(F_BYTE, 0x222, 0x00 ); // clear en + +// bic.b #0x00, &PBOUT_L ; clear sel,rw (0x222) +// or.b #0x01, &PBOUT_L ; set en (0x222) +// nop +// nop +// nop +// mov.b &PAOUT_H, DATA ; read data (0x201) +// bic.b #0x01, &PBOUT_L ; clear en (0x222) + + return rdat; +} + +signed char ClearWatchDogEnable(void) +{ + + if (pmicWriteRead(0x7e,0x02) != 0x02) // vmon_mode reg + return(STATUS_ERROR); + + return(STATUS_OK); // wdt clear, sbw set +} + + +//---------------------------------------------------------------------------- +/* Function to take target device under JTAG control. Disables the target + watchdog. Sets the global DEVICE variable as read from the target device. + Arguments: None + Result: word (STATUS_ERROR if fuse is blown, incorrect JTAG ID or + synchronizing time-out; STATUS_OK otherwise) +*/ +word GetDevice_430Xv2(void) +{ + uint32_t t1_val, t2_val, t3_val, t4_val; + int32_t i; + word wd_stat; + unsigned long flags = 0; + + i = 20; + DisableInterrupts(flags); + do { + t1_val = hres_get_counter(); + if(GetCoreID () != STATUS_OK) { + printk("GetCoreID failed.\n"); + } + + t2_val = hres_get_counter(); + if(SyncJtag_AssertPor() != STATUS_OK) { + printk("SyncJtag_AssertPor failed.\n"); + } + + t3_val = hres_get_counter(); + wd_stat = ClearWatchDogEnable(); + t4_val = hres_get_counter(); + + // disable write protection... + WriteMem_430Xv2(F_WORD, 0x120, 0xa500 ); + WriteMem_430Xv2(F_WORD, 0x122, 0x0000 ); + + pmicWriteRead(0x2c,0x00); // 1-wire mode_status: clr DQ + + printk("T1 %lu T2 %lu T3 %lu Total %lu\n", + hres_get_delta_usec(t1_val, t2_val)/USEC_PER_MSEC, + hres_get_delta_usec(t2_val, t3_val)/USEC_PER_MSEC, + hres_get_delta_usec(t3_val, t4_val)/USEC_PER_MSEC, + hres_get_delta_usec(t1_val, t4_val)/USEC_PER_MSEC); + t1_val = t2_val = t3_val = t4_val = 0; + if ( wd_stat == STATUS_OK ) { + break; + } + else { + printk("ClearWatchDogEnable failed.\n"); + } + } while(--i); + + EnableInterrupts(flags); + + if (i == 0) return(STATUS_ERROR); + + // CPU is now in Full-Emulation-State + + // TEMP-HACK: CPU now under JTAG control and will transition to sleep; release WAKEUP... + //ClrSBWAKEUP(); + + // read DeviceId from memory + ReadMemQuick_430Xv2(DeviceIdPointer + 4, 1, (word*)&DeviceId); + + return(STATUS_OK); +} + + +//---------------------------------------------------------------------------- +/* Function to release the target device from JTAG control + Argument: word Addr (0xFFFE: Perform Reset, means Load Reset Vector into PC, + otherwise: Load Addr into PC) + Result: None +*/ +int ReleaseDevice_430Xv2(unsigned long Addr, byte Stat) +{ + word i,prev,check,count; + word error_count = 0, cksum_cycle_count = 0; + int ret = 0; + + i = 1; + do { + ClrSBWAKEUP(); // to permit A6 firmware to clear sbw_en + + switch(Addr) + { + case V_BOR: // no longer approved for use + //// perform a BOR via JTAG - we loose control of the device then... + //IR_Shift(IR_TEST_REG); + //DR_Shift16(0x0200); + //MsDelay(5); // wait some time before doing any other action + // JTAG control is lost now - GetDevice() needs to be called again to gain control. + break; + case V_RESET: + IR_Shift(IR_CNTRL_SIG_16BIT); + DR_Shift16(0x0C01); // Perform a reset + DR_Shift16(0x0401); + IR_Shift(IR_CNTRL_SIG_RELEASE); + break; + default: + SetPC_430Xv2(Addr); // Set target CPU's PC + + IR_Shift(IR_CNTRL_SIG_16BIT); + DR_Shift16(0x0401); + IR_Shift(IR_ADDR_CAPTURE); + IR_Shift(IR_CNTRL_SIG_RELEASE); + } + + ClrSBWTCK(); // exit spybiwire mode + + if (i) { // entry pass + + if (Addr == V_BOR) { // boot code + MsDelay(1000); + } + else { // mcu restart + MsDelay(250); + } + + GetDevice_430Xv2(); // halt to inquire value computed over code + check = ReadMem_430Xv2(F_WORD, 0x1A1A); // current/1a1a + prev = ReadMem_430Xv2(F_WORD, 0x1A1E); // previous/1a1e + printk(KERN_ERR "A6 checksum validation: master: 0x%04x, current: 0x%04x\n", prev, check); + + if (check != prev) { + ret = -1; + printk(KERN_ERR "Error: A6 checksum validation failed!!\n"); + } + // cheksum validated... + error_count = ReadMem_430Xv2(F_WORD, 0x1a20); // read error count + cksum_cycle_count = ReadMem_430Xv2(F_WORD, 0x1a22); // read cksum cycle count + printk(KERN_ERR "A6 error_count: %d; cksum cycle count: %d\n", error_count, cksum_cycle_count); + + if (Stat == PROGRAM && 0 == ret) { + count = ReadMem_430Xv2(F_WORD, 0xFFD0); // read prog cycle count + printk(KERN_ERR "A6 program counter: %d\n", count); + WriteMem_430Xv2(F_WORD, 0xFFD0, count+1); // increment write back + } + } + + } while (i--); + + return ret; +} + +//---------------------------------------------------------------------------- +/* This function writes one byte/word at a given address ( <0xA00) + Arguments: word Format (F_BYTE or F_WORD) + word Addr (Address of data to be written) + word Data (shifted data) + Result: None +*/ +void WriteMem_430Xv2(word Format, unsigned long Addr, word Data) +{ + // Check Init State at the beginning + IR_Shift(IR_CNTRL_SIG_CAPTURE); + if(DR_Shift16(0) & 0x0301) + { + ClrTCLK(); + IR_Shift(IR_CNTRL_SIG_16BIT); + if (Format == F_WORD) + { + DR_Shift16(0x0500); + } + else + { + DR_Shift16(0x0510); + } + IR_Shift(IR_ADDR_16BIT); + DR_Shift20(Addr); + + SetTCLK(); + // New style: Only apply data during clock high phase + IR_Shift(IR_DATA_TO_ADDR); + DR_Shift16(Data); // Shift in 16 bits + ClrTCLK(); + IR_Shift(IR_CNTRL_SIG_16BIT); + DR_Shift16(0x0501); + SetTCLK(); + // one or more cycle, so CPU is driving correct MAB + ClrTCLK(); + SetTCLK(); + // Processor is now again in Init State + } +} + +//---------------------------------------------------------------------------- +/* This function writes an array of words into the target memory. + Arguments: word StartAddr (Start address of target memory) + word Length (Number of words to be programmed) + word *DataArray (Pointer to array with the data) + Result: None +*/ +void WriteMemQuick_430Xv2(unsigned long StartAddr, unsigned long Length, word *DataArray) +{ + unsigned long i; + + for (i = 0; i < Length; i++) + { + WriteMem_430Xv2(F_WORD, StartAddr, DataArray[i]); + StartAddr += 2; + } +} + +//---------------------------------------------------------------------------- +/* This function programs a set of data arrays of words into a Memeory + by using the "WriteMemQuick()" function. It conforms with the + "CodeArray" structure convention of file "Target_Code.s43" or "Target_Code.h". + Arguments: const unsigned int *DataArray (Pointer to array with the data) + const unsigned long *address (Pointer to array with the startaddresses) + const unsigned long *length_of_sections (Pointer to array with the number of words counting from startaddress) + const unsigned long sections (Number of sections in code file) + Result: word (STATUS_OK if verification was successful, + STATUS_ERROR otherwise) +*/ +word WriteAllSections_430Xv2(const unsigned short *data, const unsigned long *address, const unsigned long *length_of_sections, const unsigned long sections) +{ + int i, pos = 0; + //time_t start_time; + + //start_time = current_time(); + + printk("\n\n"); + for(i = 0; i < (int)sections; i++) + { + printk("WriteMemQuick: addr: 0x%lx; length(d): %ld", address[i], length_of_sections[i]); + /*printk("; first 4 bytes: 0x%02x 0x%02x 0x%02x 0x%02x", + *(char*)&data[pos], *((char*)&data[pos]+1), *((char*)&data[pos]+2), *((char*)&data[pos]+3)); */ + printk("\n"); + + WriteMemQuick(address[i],length_of_sections[i],(word*)&data[pos]); + pos+=length_of_sections[i]; + yield(); + } + + //printk("[WriteAllSections] elapsed time: %d ms\n", current_time() - start_time); + + + return(STATUS_OK); +} + +//---------------------------------------------------------------------------- +/* This function verifies a set of data arrays of words from Memeory + by using the "VerifyMem()" function. It conforms with the + "CodeArray" structure convention of file "Target_Code.s43" or "Target_Code.h". + Arguments: const unsigned int *DataArray (Pointer to array with the data) + const unsigned long *address (Pointer to array with the startaddresses) + const unsigned long *length_of_sections (Pointer to array with the number of words counting from startaddress) + const unsigned long sections (Number of sections in code file) + Result: word (STATUS_OK if verification was successful, + STATUS_ERROR otherwise) +*/ +word VerifyAllSections_430Xv2(const unsigned short *data, const unsigned long *address, const unsigned long *length_of_sections, const unsigned long sections) +{ + int i, pos = 0; + word ret = STATUS_OK, latched_ret = STATUS_OK; + + printk("\n\n"); + for(i = 0; i < (int)sections; i++) + { + printk("VerifyMem: addr: 0x%lx; length(d): %ld", address[i], length_of_sections[i]); + /*printk("; first 4 bytes: 0x%02x 0x%02x 0x%02x 0x%02x", + *(char*)&data[pos], *((char*)&data[pos]+1), *((char*)&data[pos]+2), *((char*)&data[pos]+3));*/ + printk("\n"); + + ret = VerifyMem(address[i],length_of_sections[i],(word*)&data[pos]); + if (STATUS_ERROR == ret) { + latched_ret = STATUS_ERROR; + printk("VerifyAllSections: Failed for section addr: 0x%lx; length(d): %ld\n", + address[i], length_of_sections[i]); + } + yield(); + + pos+=length_of_sections[i]; + } + + return(latched_ret); +} + +word TTFExtractSection_430Xv2 +(const unsigned long sec_addr, const unsigned long sec_len, + unsigned char* sec_databuf, extract_conv_fn ttf_conv, + unsigned long* sec_fmt_len) +{ + unsigned int idx; + unsigned short data; + int conv_bytes; + unsigned char* w_buf = sec_databuf; + + SetPC_430Xv2(sec_addr); + IR_Shift(IR_CNTRL_SIG_16BIT); + DR_Shift16(0x0501); + IR_Shift(IR_ADDR_CAPTURE); + + IR_Shift(IR_DATA_QUICK); + + for (idx = 0; idx < sec_len; idx++) + { + SetTCLK(); + ClrTCLK(); + data = DR_Shift16(0); // Read data from memory. + conv_bytes = ttf_conv(data, w_buf, idx); + w_buf += conv_bytes; + } + + if (idx % 8) { + sprintf(w_buf, "\x0d\x0a"); + w_buf += 2; + } + + IR_Shift(IR_CNTRL_SIG_CAPTURE); + *sec_fmt_len = (unsigned long)(w_buf - sec_databuf); + + return STATUS_OK; +} + +#define NUM_PMEM_SECTIONS (4) +#define SECTION_PREFIX_LEN (7) + +struct section_map_desc { + unsigned long sec_addr[NUM_PMEM_SECTIONS]; + unsigned long sec_len[NUM_PMEM_SECTIONS]; + void* sec_databuf[NUM_PMEM_SECTIONS]; + unsigned long sec_fmt_len[NUM_PMEM_SECTIONS]; +}; + +struct section_map_desc ttf_extract_smap = +{ + .sec_addr = {0x1400, 0x1800, 0x1c00, 0xc800}, + .sec_len = {0x200, 0x200, 0x200, 0x1c00}, // double-byte length +}; + +int ttf_extract_conv_fn + (const unsigned short inp_data, unsigned char* op_data, unsigned int count) +{ + int ret; + + ret = sprintf(op_data, "%02X %02X ", + (unsigned char)inp_data, + (unsigned char)(inp_data >> 8)); + if (0 == (count+1) % 8) { + ret += sprintf(op_data + ret, "\x0d\x0a"); + } + + return ret; +} + +word TTFExtractAllSections_430Xv2(void) +{ + int idx = 0, hdr_len; + unsigned char* sec_buf; + unsigned long sec_fmt_len = 0; + + for (idx = 0; idx < NUM_PMEM_SECTIONS; idx++) { + /* allocate buffers */ + sec_buf = kzalloc( + (ttf_extract_smap.sec_len[idx] * 6) + // 4 digits + 2 spaces + ((ttf_extract_smap.sec_len[idx] / 8) * 2) + // newline for row of 8 + 0x14, // overhead + GFP_KERNEL); + ttf_extract_smap.sec_databuf[idx] = sec_buf; + + hdr_len = sprintf(sec_buf, "@%04hX\x0d\x0a", + (unsigned short)ttf_extract_smap.sec_addr[idx]); + TTFExtractSection( + ttf_extract_smap.sec_addr[idx], + ttf_extract_smap.sec_len[idx], + sec_buf + hdr_len, + ttf_extract_conv_fn, + &sec_fmt_len); + ttf_extract_smap.sec_fmt_len[idx] = sec_fmt_len + SECTION_PREFIX_LEN; + + printk(KERN_ERR "Section Start (fmt len: %ld)\n", sec_fmt_len); + } + + return STATUS_OK; +} + +int TTFImageRead_430Xv2(char *buf, size_t count, loff_t *ppos) +{ + int idx, s_off, b_off = 0, ret; + unsigned int accum_size = 0, copy_size = 0, rem_count = count; + loff_t offset = *ppos; + + for (idx = 0; idx < NUM_PMEM_SECTIONS; idx++) { + if (offset < (accum_size + ttf_extract_smap.sec_fmt_len[idx])) { + s_off = offset - accum_size; + copy_size = ((ttf_extract_smap.sec_fmt_len[idx] - s_off) > rem_count) ? + rem_count : (ttf_extract_smap.sec_fmt_len[idx] - s_off); + ret = copy_to_user(buf + b_off, + ((char*)ttf_extract_smap.sec_databuf[idx]) + s_off, + copy_size); + rem_count -= copy_size; + offset += copy_size; + b_off += copy_size; + } + + if (!rem_count) break; + accum_size += ttf_extract_smap.sec_fmt_len[idx]; + } + + *ppos = offset; + return (count - rem_count); +} + + +word TTFExtractCacheClear_430Xv2(void) +{ + int idx = 0; + + for (idx = 0; idx < NUM_PMEM_SECTIONS; idx++) { + /* free buffers */ + if (ttf_extract_smap.sec_databuf[idx]) { + kfree(ttf_extract_smap.sec_databuf[idx]); + ttf_extract_smap.sec_databuf[idx] = NULL; + ttf_extract_smap.sec_fmt_len[idx] = 0; + } + } + + return STATUS_OK; +} + + +int GetChecksumData_430Xv2(unsigned short* cksum1, unsigned short* cksum2, + unsigned short* cksum_cycles, unsigned short* cksum_errors) +{ + word prev, check; + word error_count = 0, cycle_count = 0; + int ret = 0; + word addr; + + InitTarget(); + // halt to inquire value computed over code + GetDevice_430Xv2(); + + check = ReadMem_430Xv2(F_WORD, 0x1A1A); // current/1a1a + prev = ReadMem_430Xv2(F_WORD, 0x1A1E); // previous/1a1e + error_count = ReadMem_430Xv2(F_WORD, 0x1a20); // read error count + cycle_count = ReadMem_430Xv2(F_WORD, 0x1a22); // read cksum cycle count + + if (cksum1) *cksum1 = prev; + if (cksum2) *cksum2 = check; + if (cksum_cycles) *cksum_cycles = cycle_count; + if (cksum_errors) *cksum_errors = error_count; + + // reset + addr = ReadMem_430Xv2(F_WORD, V_RESET); + SetPC_430Xv2(addr); // Set target CPU's PC + IR_Shift(IR_CNTRL_SIG_16BIT); + DR_Shift16(0x0401); + IR_Shift(IR_ADDR_CAPTURE); + IR_Shift(IR_CNTRL_SIG_RELEASE); + + MsDelay(1000); + + return ret; +} +//---------------------------------------------------------------------------- +/* This function reads one byte/word from a given address in memory + Arguments: word Format (F_BYTE or F_WORD) + word Addr (address of memory) + Result: word (content of the addressed memory location) +*/ +word ReadMem_430Xv2(word Format, unsigned long Addr) +{ + word TDOword = 0; + + // Check Init State at the beginning + IR_Shift(IR_CNTRL_SIG_CAPTURE); + if(DR_Shift16(0) & 0x0301) + { + // Read Memory + ClrTCLK(); + IR_Shift(IR_CNTRL_SIG_16BIT); + if (Format == F_WORD) + { + DR_Shift16(0x0501); // Set word read + } + else + { + DR_Shift16(0x0511); // Set byte read + } + IR_Shift(IR_ADDR_16BIT); + DR_Shift20(Addr); // Set address + IR_Shift(IR_DATA_TO_ADDR); + SetTCLK(); + ClrTCLK(); + TDOword = DR_Shift16(0x0000); // Shift out 16 bits + + SetTCLK(); + // one or more cycle, so CPU is driving correct MAB + ClrTCLK(); + SetTCLK(); + // Processor is now again in Init State + } + + return TDOword; +} + +//---------------------------------------------------------------------------- +/* This function reads an array of words from a memory. + Arguments: word StartAddr (Start address of memory to be read) + word Length (Number of words to be read) + word *DataArray (Pointer to array for the data) + Result: None +*/ +void ReadMemQuick_430Xv2(unsigned long StartAddr, unsigned long Length, word *DataArray) +{ + unsigned long i; + + SetPC_430Xv2(StartAddr); + IR_Shift(IR_CNTRL_SIG_16BIT); + DR_Shift16(0x0501); + IR_Shift(IR_ADDR_CAPTURE); + + IR_Shift(IR_DATA_QUICK); + + for (i = 0; i < Length; i++) + { + SetTCLK(); + ClrTCLK(); + *DataArray++ = DR_Shift16(0); // Read data from memory. + } + IR_Shift(IR_CNTRL_SIG_CAPTURE); +} + +//---------------------------------------------------------------------------- +/* This function performs a Verification over the given memory range + Arguments: word StartAddr (Start address of memory to be verified) + word Length (Number of words to be verified) + word *DataArray (Pointer to array with the data) + Result: word (STATUS_OK if verification was successful, STATUS_ERROR otherwise) +*/ +word VerifyMem_430Xv2(unsigned long StartAddr, unsigned long Length, word *DataArray) +{ + unsigned long i; + word Data; + char err_flag = 0; + + SetPC_430Xv2(StartAddr); + IR_Shift(IR_CNTRL_SIG_16BIT); + DR_Shift16(0x0501); + IR_Shift(IR_ADDR_CAPTURE); + + IR_Shift(IR_DATA_QUICK); + + for (i = 0; i < Length; i++) + { + SetTCLK(); + ClrTCLK(); + Data = DR_Shift16(0); // Read data from memory. + + if(Data != DataArray[i]) + { + printk("VerifyMem failed. Idx; 0x%lx; Expected: 0x%04x; Read; 0x%04x\n", i, DataArray[i], Data); + err_flag = 1; + //break; + } + } + IR_Shift(IR_CNTRL_SIG_CAPTURE); + + return((err_flag == 0) ? STATUS_OK : STATUS_ERROR); +} + +/****************************************************************************/ +/* END OF SOURCE FILE */ +/****************************************************************************/ diff --git a/drivers/misc/a6/jtag_funcs.h b/drivers/misc/a6/jtag_funcs.h new file mode 100644 index 00000000000..467b2b4885c --- /dev/null +++ b/drivers/misc/a6/jtag_funcs.h @@ -0,0 +1,126 @@ +#ifndef __BYTEWORD__ +#define __BYTEWORD__ +typedef unsigned int word; +typedef unsigned char byte; +#endif + +/****************************************************************************/ +/* Define section for constants */ +/****************************************************************************/ + +// Constants for the JTAG instruction register (IR, requires LSB first). +// The MSB has been interchanged with LSB due to use of the same shifting +// function as used for the JTAG data register (DR, requires MSB first). + +// Instructions for the JTAG control signal register +#define IR_CNTRL_SIG_16BIT 0xC8 // 0x13 original values +#define IR_CNTRL_SIG_CAPTURE 0x28 // 0x14 +#define IR_CNTRL_SIG_RELEASE 0xA8 // 0x15 +// Instructions for the JTAG Fuse +#define IR_PREPARE_BLOW 0x44 // 0x22 +#define IR_EX_BLOW 0x24 // 0x24 +// Instructions for the JTAG data register +#define IR_DATA_16BIT 0x82 // 0x41 +#define IR_DATA_QUICK 0xC2 // 0x43 +// Instructions for the JTAG PSA mode +#define IR_DATA_PSA 0x22 // 0x44 +#define IR_SHIFT_OUT_PSA 0x62 // 0x46 +// Instructions for the JTAG address register +#define IR_ADDR_16BIT 0xC1 // 0x83 +#define IR_ADDR_CAPTURE 0x21 // 0x84 +#define IR_DATA_TO_ADDR 0xA1 // 0x85 +// Bypass instruction +#define IR_BYPASS 0xFF // 0xFF + +// JTAG identification value for all existing Flash-based MSP430 devices +#define JTAG_ID 0x89 +#define JTAG_ID91 0x91 +// Jtag 17 +#define DEVICE_HAS_JTAG17 1 + +// additional instructions for JTAG_ID91 architectures +#define IR_COREIP_ID 0xE8 // 0x17 +#define IR_DEVICE_ID 0xE1 // 0x87 +// Instructions for the JTAG mailbox +#define IR_JMB_EXCHANGE 0x86 // 0x61 +#define IR_TEST_REG 0x54 // 0x2A + +// Constants for JTAG mailbox data exchange +#define OUT1RDY 0x0008 +#define IN0RDY 0x0001 +#define JMB32B 0x0010 +#define OUTREQ 0x0004 +#define INREQ 0x0001 + +// Constants for data formats, dedicated addresses +#ifndef __DATAFORMATS__ +#define __DATAFORMATS__ +#define F_BYTE 8 +#define F_WORD 16 +#define F_ADDR 20 +#define F_LONG 32 +#endif +#define V_RESET 0xFFFE +#define V_BOR 0x1B08 + +// Constants for VPP connection at Blow-Fuse +#define VPP_ON_TDI 0 +#define VPP_ON_TEST 1 + +// ReleaseDevice parameters +#define INIT 0 // ReleaseDevice() status +#define ERROR 1 // inc verify error count +#define VERIFY 2 // inc verify pass count +#define PROGRAM 3 // inc reprogram count + +/****************************************************************************/ +/* Function prototypes */ +/****************************************************************************/ + +// Low level JTAG functions +//static word DR_Shift16(word Data); +//static unsigned long DR_Shift20(unsigned long address); +//static word IR_Shift(byte Instruction); +//static void ResetTAP(void); +//static word ExecutePOR_430Xv2(void); +//static void SetPC_430Xv2(unsigned long Addr); +word VerifyPSA_430Xv2(unsigned long StartAddr, unsigned long Length, word *DataArray); + +// High level JTAG functions +word GetDevice_430Xv2(void); +#define GetDevice GetDevice_430Xv2 +int ReleaseDevice_430Xv2(unsigned long Addr, byte Stat); +#define ReleaseDevice ReleaseDevice_430Xv2 +void WriteMem_430Xv2(word Format, unsigned long Addr, word Data); +#define WriteMem WriteMem_430Xv2 +void WriteMemQuick_430Xv2(unsigned long StartAddr, unsigned long Length, word *DataArray); +#define WriteMemQuick WriteMemQuick_430Xv2 +word WriteAllSections_430Xv2(const unsigned short *data, const unsigned long *address, const unsigned long *length_of_sections, const unsigned long sections); +#define WriteAllSections WriteAllSections_430Xv2 +word VerifyAllSections_430Xv2(const unsigned short *data, const unsigned long *address, const unsigned long *length_of_sections, const unsigned long sections); +#define VerifyAllSections VerifyAllSections_430Xv2 +word ReadMem_430Xv2(word Format, unsigned long Addr); +#define ReadMem ReadMem_430Xv2 +void ReadMemQuick_430Xv2(unsigned long StartAddr, unsigned long Length, word *DataArray); +#define ReadMemQuick ReadMemQuick_430Xv2 +word VerifyMem_430Xv2(unsigned long StartAddr, unsigned long Length, word *DataArray); +#define VerifyMem VerifyMem_430Xv2 + + +typedef int (*extract_conv_fn)( const unsigned short inp_data, + unsigned char* op_data, unsigned int count); + +word TTFExtractSection_430Xv2 + (const unsigned long sec_addr, const unsigned long sec_len, + unsigned char* sec_databuf, extract_conv_fn ttf_conv, + unsigned long* sec_fmt_len); +#define TTFExtractSection TTFExtractSection_430Xv2 +word TTFExtractAllSections_430Xv2(void); +#define TTFExtractAllSections TTFExtractAllSections_430Xv2 +word TTFExtractCacheClear_430Xv2(void); +#define TTFExtractCacheClear TTFExtractCacheClear_430Xv2 +int TTFImageRead_430Xv2(char *buf, size_t count, loff_t *ppos); +#define TTFImageRead TTFImageRead_430Xv2 +int GetChecksumData_430Xv2(unsigned short* cksum1, unsigned short* cksum2, + unsigned short* cksum_cycles, unsigned short* cksum_errors); +#define GetChecksumData GetChecksumData_430Xv2 \ No newline at end of file diff --git a/drivers/misc/a6/low_level_funcs.c b/drivers/misc/a6/low_level_funcs.c new file mode 100644 index 00000000000..846045ee4fe --- /dev/null +++ b/drivers/misc/a6/low_level_funcs.c @@ -0,0 +1,241 @@ +#include "a6_host_adapter.h" +#include "low_level_funcs.h" + +// declarations for active implementation mappings used by the sbw code... +extern uint16_t (*SetSBWTCK)(void); +extern uint16_t (*ClrSBWTCK)(void); +extern uint16_t (*SetSBWTDIO)(void); +extern uint16_t (*ClrSBWTDIO)(void); +extern uint16_t (*SetInSBWTDIO)(void); +extern uint16_t (*SetOutSBWTDIO)(void); +extern uint16_t (*GetSBWTDIO)(void); +extern uint16_t (*SetSBWAKEUP)(void); +extern uint16_t (*ClrSBWAKEUP)(void); +extern void (*delay)(uint32_t delay_us); +// + +byte tdo_bit; //holds the value of TDO-bit +byte TCLK_saved = 1; // holds the last value of TCLK before entering a JTAG sequence + +/****************************************************************************/ +/* Function declarations which have to be programmed by the user for use */ +/* with hosts other than the MSP430F149. */ +/* */ +/* The following MSP430F149-specific code can be used as a reference as to */ +/* how to implement the required JTAG communication on additional hosts. */ +/****************************************************************************/ + +//------------------------------- +// combinations of sbw-cycles (TMS, TDI, TDO) +void TMSL_TDIL(void) +{ + unsigned long flags = 0; + + DisableInterrupts(flags); + TMSL TDIL TDOsbw + EnableInterrupts(flags); + +} +//--------------------------------- +void TMSH_TDIL(void) +{ + unsigned long flags = 0; + + DisableInterrupts(flags); + TMSH TDIL TDOsbw + EnableInterrupts(flags); +} +//------------------------------------ +void TMSL_TDIH(void) +{ + unsigned long flags = 0; + + DisableInterrupts(flags); + TMSL TDIH TDOsbw + EnableInterrupts(flags); +} +//------------------------------------- +void TMSH_TDIH(void) +{ + unsigned long flags = 0; + + DisableInterrupts(flags); + TMSH TDIH TDOsbw + EnableInterrupts(flags); +} +//------------------------------------ +void TMSL_TDIH_TDOrd(void) +{ + unsigned long flags = 0; + + DisableInterrupts(flags); + TMSL TDIH TDO_RD + EnableInterrupts(flags); +} +//------------------------------------ +void TMSL_TDIL_TDOrd(void) +{ + unsigned long flags = 0; + + DisableInterrupts(flags); + TMSL TDIL TDO_RD + EnableInterrupts(flags); +} +//------------------------------------ +void TMSH_TDIH_TDOrd(void) +{ + unsigned long flags = 0; + + DisableInterrupts(flags); + TMSH TDIH TDO_RD + EnableInterrupts(flags); +} +//------------------------------------ +void TMSH_TDIL_TDOrd(void) +{ + unsigned long flags = 0; + + DisableInterrupts(flags); + TMSH TDIL TDO_RD + EnableInterrupts(flags); +} +//---------------------------------------------- +// enters with TCLK_saved and exits with TCLK = 0 +void ClrTCLK_sbw(void) +{ + unsigned long flags = 0; + + DisableInterrupts(flags); + if (TCLK_saved) + { + TMSLDH + } + else + { + TMSL + } + + ClrSBWTDIO(); + + TDIL TDOsbw //ExitTCLK + TCLK_saved = 0; + EnableInterrupts(flags); +} + +//---------------------------------------------- +// enters with TCLK_saved and exits with TCLK = 1 +void SetTCLK_sbw(void) +{ + unsigned long flags = 0; + + DisableInterrupts(flags); + if (TCLK_saved) + { + TMSLDH + } + else + { + TMSL + } + + SetSBWTDIO(); + + TDIH TDOsbw //ExitTCLK + TCLK_saved = 1; + EnableInterrupts(flags); +} + +//---------------------------------------------------------------------------- +/* Shift a value into TDI (MSB first) and simultaneously shift out a value + from TDO (MSB first). + Arguments: word Format (number of bits shifted, 8 (F_BYTE), 16 (F_WORD), + 20 (F_ADDR) or 32 (F_LONG)) + unsigned long Data (data to be shifted into TDI) + Result: unsigned long (scanned TDO value) +*/ + +unsigned long AllShifts(word Format, unsigned long Data) +{ + unsigned long TDOword = 0x00000000; + unsigned long MSB = 0x00000000; + word i; + //unsigned long flags = 0; + + //DisableInterrupts(flags); + switch(Format) + { + case F_BYTE: MSB = 0x00000080; + break; + case F_WORD: MSB = 0x00008000; + break; + case F_ADDR: MSB = 0x00080000; + break; + case F_LONG: MSB = 0x80000000; + break; + default: // this is an unsupported format, function will just return 0 + //EnableInterrupts(flags); + return TDOword; + } + // shift in bits + for (i = Format; i > 0; i--) + { + if (i == 1) // last bit requires TMS=1; TDO one bit before TDI + { + ((Data & MSB) == 0) ? TMSH_TDIL_TDOrd() : TMSH_TDIH_TDOrd(); + } + else + { + ((Data & MSB) == 0) ? TMSL_TDIL_TDOrd() : TMSL_TDIH_TDOrd(); + } + Data <<= 1; + if (tdo_bit) + TDOword++; + if (i > 1) + TDOword <<= 1; // TDO could be any port pin + } + TMSH_TDIH(); // update IR + if (TCLK_saved) + { + TMSL_TDIH(); + } + else + { + TMSL_TDIL(); + } + + // de-scramble bits on a 20bit shift + if(Format == F_ADDR) + { + TDOword = ((TDOword << 16) + (TDOword >> 4)) & 0x000FFFFF; + } + //EnableInterrupts(flags); + + return(TDOword); +} + +void DrvSignals(void) +{ + SetSBWTDIO(); + ClrSBWTCK(); +} + +void RlsSignals(void) +{ + SetSBWTDIO(); + ClrSBWTCK(); +} + +void InitTarget(void) +{ + DrvSignals(); +} + +void ReleaseTarget(void) +{ + RlsSignals(); +} + + +/****************************************************************************/ +/* END OF SOURCE FILE */ +/****************************************************************************/ diff --git a/drivers/misc/a6/low_level_funcs.h b/drivers/misc/a6/low_level_funcs.h new file mode 100644 index 00000000000..a748ec74eca --- /dev/null +++ b/drivers/misc/a6/low_level_funcs.h @@ -0,0 +1,71 @@ +#ifndef _low_level_funcs_h_ +#define _low_level_funcs_h_ + +/****************************************************************************/ +/* Macros and Pin-to-Signal assignments which have to be programmed */ +/* by the user. This implementation assumes use of an MSP430F149 as the host*/ +/* controller and the corresponding hardware given in the application */ +/* report TBD Appendix A. */ +/* */ +/* The following MSP430 example acts as a hint of how to generally */ +/* implement a micro-controller programmer solution for the MSP430 flash- */ +/* based devices. */ +/****************************************************************************/ + +#ifndef __BYTEWORD__ +#define __BYTEWORD__ +typedef unsigned int word; +typedef unsigned char byte; +#endif + +//---------------------------------------------------------------------------- +// Pin-to-Signal Assignments +//---------------------------------------------------------------------------- + +#define TMSH {SetSBWTDIO(); nNOPS ClrSBWTCK(); nNOPS SetSBWTCK();}// TMS = 1 +#define TMSL {ClrSBWTDIO(); nNOPS ClrSBWTCK(); nNOPS SetSBWTCK();} // TMS = 0 +#define TMSLDH {ClrSBWTDIO(); nNOPS ClrSBWTCK(); nNOPS SetSBWTDIO(); SetSBWTCK();} // TMS = 0, then TCLK(TDI) immediately = 1 +#define TDIH {SetSBWTDIO(); nNOPS ClrSBWTCK(); nNOPS SetSBWTCK();} // TDI = 1 +#define TDIL {ClrSBWTDIO(); nNOPS ClrSBWTCK(); nNOPS SetSBWTCK();} // TDI = 0 +#define TDOsbw {SetSBWTDIO();SetInSBWTDIO(); nNOPS ClrSBWTCK(); nNOPS SetSBWTCK(); SetOutSBWTDIO();} // TDO cycle without reading TDO +#define TDO_RD {SetSBWTDIO();SetInSBWTDIO(); nNOPS ClrSBWTCK(); nNOPS tdo_bit = GetSBWTDIO(); SetSBWTCK(); SetOutSBWTDIO();} // TDO cycle with TDO read + + void ClrTCLK_sbw(void); + void SetTCLK_sbw(void); + #define ClrTCLK() ClrTCLK_sbw() + #define SetTCLK() SetTCLK_sbw() + + #define SetRST() SetSBWTDIO() + #define ClrRST() ClrSBWTDIO() + #define ReleaseRST() () + #define SetTST() SetSBWTCK() + #define ClrTST() ClrSBWTCK() + + +/*---------------------------------------------------------------------------- + Definition of global variables +*/ +extern byte TCLK_saved; // holds the last value of TCLK before entering a JTAG sequence + + +/*---------------------------------------------------------------------------- + Low Level function prototypes +*/ + +void TMSL_TDIL(void); +void TMSH_TDIL(void); +void TMSL_TDIH(void); +void TMSH_TDIH(void); +void TMSL_TDIH_TDOrd(void); +void TMSL_TDIL_TDOrd(void); +void TMSH_TDIH_TDOrd(void); +void TMSH_TDIL_TDOrd(void); + +unsigned long AllShifts(word Format, unsigned long Data); +void DrvSignals(void); +void RlsSignals(void); +void InitTarget(void); +void ReleaseTarget(void); + + +#endif diff --git a/drivers/misc/hres_counter.c b/drivers/misc/hres_counter.c new file mode 100644 index 00000000000..0dd507b3b05 --- /dev/null +++ b/drivers/misc/hres_counter.c @@ -0,0 +1,575 @@ + /* + * linux/drivers/misc/hres_counter.c + * + * Copyright (C) 2008 Palm, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define NUM_CHANNELS 32 +#define NUM_EVENTS 512 + +struct count_ch { + u32 count; + u32 total; + u32 tstamp; + u32 active; +}; + +struct hres_event { + u32 tstamp; + char* type; + u32 arg1; + u32 arg2; +}; + +struct dev_ctxt { + void *timer; + int suspended; + struct platform_device *pdev; + struct count_ch ch[NUM_CHANNELS]; + + int (*init_hres_timer)(void **); + int (*release_hres_timer)(void *); + int (*suspend_hres_timer)(void *); + int (*resume_hres_timer)(void *); + + u32 (*read_hres_timer)(void *); + u32 (*convert_hres_timer)(u32); +}; + +static struct dev_ctxt *gdev = NULL; + +static int evlog_on = 1; +static int evlog_cnt = 0; +static int evlog_num = 0; +static struct hres_event evlog[NUM_EVENTS]; + + +/* + * + */ +static inline u32 +hres_read_tick ( void ) { + if( unlikely(gdev == NULL)) + return 0; + + return gdev->read_hres_timer( gdev->timer ); +} + +/* + * Current value of high res counter + */ +u32 +hres_get_counter ( void ) +{ + return hres_read_tick(); +} +EXPORT_SYMBOL(hres_get_counter); + +/* + * Return the delta in useconds from start to end + */ +u32 +hres_get_delta_usec ( u32 start, u32 end ) +{ + return gdev->convert_hres_timer(end - start); +} + +/* + * Reset channel + */ +void +hres_ch_reset ( uint ch ) +{ + if( unlikely(gdev == NULL)) + return; + + if( unlikely(ch >= NUM_CHANNELS)) + return; + + memset ( &gdev->ch[ch], 0, sizeof(struct count_ch)); +} +EXPORT_SYMBOL(hres_ch_reset); + +/* + * Increment Event count + */ +void +hres_event_cnt ( uint ch ) +{ + unsigned long flags; + + if( unlikely(gdev == NULL)) + return; + + if( unlikely(ch >= NUM_CHANNELS)) + return; + + local_irq_save(flags); + gdev->ch[ch].count++; + local_irq_restore(flags); +} +EXPORT_SYMBOL(hres_event_cnt); + +/* + * Mark Event Start + */ +void +hres_event_start ( uint ch ) +{ + unsigned long flags; + + if( unlikely(gdev == NULL)) + return; + + if( unlikely(ch >= NUM_CHANNELS)) + return; + + local_irq_save(flags); + gdev->ch[ch].tstamp = hres_read_tick(); + gdev->ch[ch].active = 1; + local_irq_restore(flags); +} +EXPORT_SYMBOL(hres_event_start); + +/* + * Mark Event End and count + */ +u32 +hres_event_end ( uint ch ) +{ + u32 ts = 0; + unsigned long flags; + + if( unlikely(gdev == NULL)) + return 0; + + if( unlikely(ch >= NUM_CHANNELS)) + return 0; + + if(!gdev->ch[ch].active) + return 0; + + local_irq_save(flags); + ts = gdev->read_hres_timer( gdev->timer ) - gdev->ch[ch].tstamp; + gdev->ch[ch].total += ts; + gdev->ch[ch].count++; + gdev->ch[ch].active = 0; + local_irq_restore(flags); + return ts; + +} +EXPORT_SYMBOL(hres_event_end); + + +/* + * Mark Event End and count + */ +void +hres_event ( char *type, u32 arg1, u32 arg2 ) +{ + unsigned long flags; + struct hres_event *evt; + + if( unlikely(gdev == NULL)) + return; + + if( !evlog_on ) + return; + + local_irq_save(flags); + evt = evlog + evlog_cnt; + evlog_cnt++; + if( evlog_cnt > evlog_num ) + evlog_num = evlog_cnt; + if( evlog_cnt == NUM_EVENTS ) + evlog_cnt = 0; + evt->tstamp = hres_read_tick(); + evt->type = type; + evt->arg1 = arg1; + evt->arg2 = arg2; + local_irq_restore(flags); + return; + +} +EXPORT_SYMBOL(hres_event); + + +/* + * + */ +void +hres_evlog_reset(void) +{ + unsigned long flags; + + local_irq_save(flags); + evlog_cnt = 0; + evlog_num = 0; + local_irq_restore(flags); +} +EXPORT_SYMBOL(hres_evlog_reset); + + +/* + * Enable logging + */ +int +hres_evlog_enable(void) +{ + int rc = evlog_on; + evlog_on = 1; + return rc; +} +EXPORT_SYMBOL(hres_evlog_enable); + +/* + * Disable logging + */ +int +hres_evlog_disable(void) +{ + int rc = evlog_on; + evlog_on = 0; + return rc; +} +EXPORT_SYMBOL(hres_evlog_disable); + +/* + * Print log + */ +void +hres_evlog_print ( void ) +{ + int i; + u32 last_tstamp = 0; + struct hres_event *evt = evlog; + + printk ("evlog: beg\n"); + for (i = 0; i < evlog_num; i++, evt++ ) { + printk ( "%010d (d%10d): %12d (0x%08x) %12d (0x%08x) %s\n", + gdev->convert_hres_timer(evt->tstamp), + gdev->convert_hres_timer(evt->tstamp - last_tstamp), + evt->arg1, evt->arg1, + evt->arg2, evt->arg2, + evt->type ); + last_tstamp = evt->tstamp; + } + printk ("evlog: end\n"); +} +EXPORT_SYMBOL(hres_evlog_print); + + +/* + * Sysfs + */ +static ssize_t +counter_show ( struct device *dev, + struct device_attribute *attr, + char *buf ) +{ + return snprintf( buf, PAGE_SIZE, "%d\n", hres_read_tick()); +} + +static DEVICE_ATTR( counter, S_IRUGO | S_IWUSR, counter_show, NULL ); + + + +static ssize_t +evlog_show ( struct device *dev, + struct device_attribute *attr, + char *buf ) +{ + hres_evlog_print (); + return 0; +} + +static ssize_t +evlog_store( struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count ) +{ + hres_evlog_reset(); + return count; +} + + +static DEVICE_ATTR( evlog, S_IRUGO | S_IWUSR, evlog_show, evlog_store ); + + + +static ssize_t +channels_show ( struct device *dev, + struct device_attribute *attr, + char *buf ) +{ + int ch; + ssize_t len = 0; + + for ( ch = 0; ch < NUM_CHANNELS; ch++ ) { + len += snprintf( buf + len, PAGE_SIZE - len, + "%02d: %010u %4d\n", ch, + gdev->convert_hres_timer(gdev->ch[ch].total), + gdev->ch[ch].count + ); + } + return len; +} + +static ssize_t +channels_store( struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count ) +{ + int ch; + + ch = simple_strtol ( buf, NULL, 10 ); + if( ch == -1 ) { + for ( ch = 0; ch < NUM_CHANNELS; ch++ ) + hres_ch_reset ( ch ); + } + else { + hres_ch_reset ((uint) ch ); + } + return count; +} + +static DEVICE_ATTR( channels, S_IRUGO | S_IWUSR, channels_show, channels_store); + + +static void +hres_sysrq_show_evlog(int key, struct tty_struct *tty) +{ + hres_evlog_print(); +} + + +/* + * + */ +static int +hres_panic(struct notifier_block *this, unsigned long event, void *ptr) +{ + hres_evlog_print(); + + return NOTIFY_DONE; +} + +static struct notifier_block panic_block = { + .notifier_call = hres_panic, +}; + +/* + * Sys req related + */ +static struct sysrq_key_op sysrq_show_log_op = { + .handler = hres_sysrq_show_evlog, + .help_msg = "show-evLog", + .action_msg = "Show HiRes EvLog", +}; + +static void +hres_sysrq_reset_evlog(int key, struct tty_struct *tty) +{ + hres_evlog_reset(); +} + + +static struct sysrq_key_op sysrq_reset_log_op = { + .handler = hres_sysrq_reset_evlog, + .help_msg = "reset-evlog(c)", + .action_msg = "Reset Hires EvLog", +}; + +/* + * + */ +static int __devexit +hres_counter_remove ( struct platform_device *pdev ) +{ + struct dev_ctxt *ctxt = gdev; + + gdev = NULL; + + device_remove_file ( &pdev->dev, &dev_attr_counter ); + device_remove_file ( &pdev->dev, &dev_attr_channels ); + device_remove_file ( &pdev->dev, &dev_attr_evlog ); + + gdev->release_hres_timer ( ctxt->timer ); + + platform_set_drvdata( pdev, NULL ); + + kfree ( ctxt ); + + return 0; +} + + + +/* + * + */ +static int __init +hres_counter_probe ( struct platform_device *pdev ) +{ + int rc; + struct dev_ctxt *ctxt; + struct hres_counter_platform_data *pdata = pdev->dev.platform_data; + + ctxt = kzalloc ( sizeof(struct dev_ctxt), GFP_KERNEL ); + if( ctxt == NULL ) + goto ret_nodev; + + ctxt->pdev = pdev; + ctxt->init_hres_timer = pdata->init_hres_timer; + ctxt->release_hres_timer = pdata->release_hres_timer; + ctxt->read_hres_timer = pdata->read_hres_timer; + ctxt->convert_hres_timer = pdata->convert_hres_timer; + + ctxt->suspend_hres_timer = pdata->suspend_hres_timer; + ctxt->resume_hres_timer = pdata->resume_hres_timer; + + rc = ctxt->init_hres_timer(&ctxt->timer); + if( rc ) + goto free_ctxt; + + platform_set_drvdata ( pdev, ctxt ); + + rc = device_create_file ( &pdev->dev, &dev_attr_counter ); + if( rc ) + goto free_timer; + + rc = device_create_file ( &pdev->dev, &dev_attr_channels ); + if( rc ) + goto free_sysfs_counter; + + rc = device_create_file ( &pdev->dev, &dev_attr_evlog ); + if( rc ) + goto free_sysfs_channels; + + gdev = ctxt; + + printk( KERN_INFO "Initialize %s device\n", + pdev->name); +#if 0 +#ifndef CONFIG_HRES_COUNTER_TIMER32K + printk( KERN_INFO "Initialize %s device (%dMHz)\n", + pdev->name, clk_rate ); +#else + printk( KERN_INFO "Initialize %s device (32KHz)\n", pdev->name); +#endif +#endif + + // register sys request key + register_sysrq_key( 'l', &sysrq_show_log_op ); + register_sysrq_key( 'c', &sysrq_reset_log_op ); + + // register panic callback + atomic_notifier_chain_register(&panic_notifier_list, &panic_block); + + return 0; + +free_sysfs_channels: + device_remove_file ( &pdev->dev, &dev_attr_channels ); +free_sysfs_counter: + device_remove_file ( &pdev->dev, &dev_attr_counter ); +free_timer: + ctxt->release_hres_timer ( ctxt->timer ); +free_ctxt: + kfree ( ctxt ); +ret_nodev: + return -ENODEV; +} + + +#ifdef CONFIG_HRES_COUNTER_TIMER32K +#undef CONFIG_PM +#endif + +#ifdef CONFIG_PM +static int +hres_counter_suspend( struct platform_device *dev, pm_message_t state ) +{ + struct dev_ctxt *ctxt = gdev; + + if( ctxt->suspended ) + return 0; + + ctxt->resume_hres_timer(gdev->timer); + + ctxt->suspended = 1; + + return 0; +} + +static int +hres_counter_resume ( struct platform_device *dev ) +{ + struct dev_ctxt *ctxt = gdev; + + if(!ctxt->suspended ) + return 0; + + ctxt->suspend_hres_timer(gdev->timer); + + ctxt->suspended = 0; + + return 0; +} +#else +#define hres_counter_suspend NULL +#define hres_counter_resume NULL +#endif /* CONFIG_PM */ + + +/* + * + */ + +static struct platform_driver hres_counter_driver = { + .driver = { + .name = "hres_counter", + }, + .probe = hres_counter_probe, + .remove = __devexit_p(hres_counter_remove), + .suspend = hres_counter_suspend, + .resume = hres_counter_resume, +}; + +static int __init +hres_counter_init(void) +{ + platform_driver_register ( &hres_counter_driver ); + return 0; +} + + +static void __exit +hres_counter_exit(void) +{ + platform_driver_unregister ( &hres_counter_driver ); + return; +} + +module_init(hres_counter_init); +module_exit(hres_counter_exit); + +MODULE_DESCRIPTION("OMAP High resolution counter driver" ); +MODULE_LICENSE("GPL"); + + diff --git a/include/linux/a6.h b/include/linux/a6.h new file mode 100644 index 00000000000..860109dc5a4 --- /dev/null +++ b/include/linux/a6.h @@ -0,0 +1,66 @@ +/* + * linux/include/linux/a6.h + * + * Driver for the A6 TP. + * + * Copyright (C) 2008 Palm, Inc. + * Author: Raj Mojumder + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License. + */ + +#ifndef _A6_H +#define _A6_H + +#include +#include + + +#define A6_DEVICE_0 "a6_0" +#define A6_DEVICE_1 "a6_1" +#define A6_DRIVER "a6" + +#define A6_DEVICE A6_DEVICE_0 + + +/* IOCTLs */ +#define A6_IOCTL_SET_FW_DATA _IOW('c', 0x01, int) +#define A6_IOCTL_VERIFY_FW_DATA _IOW('c', 0x02, int) + +/* Touch panel platform data structure */ +struct a6_platform_data { + char* dev_name; // device name + int pwr_gpio; + int sbw_tck_gpio; + int sbw_tdio_gpio; + int sbw_wkup_gpio; + void* sbw_ops; + void* wake_ops; + + void* sbw_init_gpio_config; + int sbw_init_gpio_config_size; + void* sbw_deinit_gpio_config; + int sbw_deinit_gpio_config_size; + + int (*sbw_init)(struct a6_platform_data*); + int (*sbw_deinit)(struct a6_platform_data*); +}; + +struct a6_wake_ops { + void* data; + + // external periodic sleep/wake interface + int (*enable_periodic_wake)(void *); + int (*disable_periodic_wake)(void *); + + // internal sleep/wake interface + int (*internal_wake_enable_state)(void*); + int (*internal_wake_period)(void*); + + // force sleep/wake interface (needed to force-wake A6 when + // internal/external periodic sleep/wake in effect... + int (*force_wake)(void *); + int (*force_sleep)(void *); +}; +#endif // _A6_H diff --git a/include/linux/a6_sbw_interface.h b/include/linux/a6_sbw_interface.h new file mode 100644 index 00000000000..98a81edf086 --- /dev/null +++ b/include/linux/a6_sbw_interface.h @@ -0,0 +1,45 @@ +/* + * linux/include/linux/a6_sbw_interface.h + * + * Public interface for the SBW protocol layer. Declares callbacks used by the core protocol. + * Interfaces include: + * - per-A6-device interface: every A6 device must define its own implementation of this interface. + * - per-target interfaces : each board-type must define its own implementation of these interfaces. + * - per-host system: operating system specific implementations must be defined. + * + * Copyright (C) 2008 Palm, Inc. + * Author: Raj Mojumder + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License. + */ + +#ifndef _A6_SBW_INTERFACE_H_ +#define _A6_SBW_INTERFACE_H_ + +struct a6_sbw_interface { + // per-A6-device interface (separate instantiation for every a6 device) + struct { + uint16_t (*SetSBWTCK)(void); + uint16_t (*ClrSBWTCK)(void); + uint16_t (*SetSBWTDIO)(void); + uint16_t (*ClrSBWTDIO)(void); + uint16_t (*SetInSBWTDIO)(void); + uint16_t (*SetOutSBWTDIO)(void); + uint16_t (*GetSBWTDIO)(void); + uint16_t (*SetSBWAKEUP)(void); + uint16_t (*ClrSBWAKEUP)(void); + } a6_per_device_interface; + + // per-target interface (separate instantiation for every board) + struct { + void (*delay)(uint32_t delay_us); + } a6_per_target_interface; +}; + + +// per-host system: (operating system specific) +#define a6_disable_interrupts(flags) {flags=flags;local_irq_save(flags);} +#define a6_enable_interrupts(flags) {local_irq_restore(flags);} + +#endif // _A6_SBW_INTERFACE_H_ diff --git a/include/linux/hres_counter.h b/include/linux/hres_counter.h new file mode 100644 index 00000000000..9672f1c238c --- /dev/null +++ b/include/linux/hres_counter.h @@ -0,0 +1,64 @@ +#ifndef __HRES_COUNTER_INCLUDED__ +#define __HRES_COUNTER_INCLUDED__ + +#include + +struct hres_counter_platform_data { + /* Initialize/obtain the timer resource */ + int (*init_hres_timer)(void **); + + /* Release the timer resource*/ + int (*release_hres_timer)(void *); + + /* PM functions */ + int (*suspend_hres_timer)(void *); + int (*resume_hres_timer)(void *); + + /* Read native timer count value */ + u32 (*read_hres_timer)(void *); + + /* Convert native timer value to desired human */ + /* readable format (usec or msec, etc) */ + u32 (*convert_hres_timer)(u32); +}; + +#define LOG_MMC_TIMEOUT_TIMING_MEASUREMENTS 1 +#if !defined(CONFIG_HRES_COUNTER) && LOG_MMC_TIMEOUT_TIMING_MEASUREMENTS +#error "MMC timeout measurements can only be done with hires counters" +#endif + +#ifdef CONFIG_HRES_COUNTER + +extern u32 hres_get_counter ( void ); +extern u32 hres_get_delta_usec ( u32 start, u32 end ); +extern void hres_ch_reset ( uint ch ); +extern void hres_event_cnt ( uint ch ); +extern void hres_event_start ( uint ch ); +extern u32 hres_event_end ( uint ch ); +extern void hres_event ( char *type, u32 arg1, u32 arg2 ); +extern int hres_evlog_enable ( void ); +extern int hres_evlog_disable ( void ); +extern void hres_evlog_print ( void ); +extern void hres_evlog_reset ( void ); + +#else + +#define hres_get_counter(args...) +#define hres_get_delta_usec(args...) +#define hres_ch_reset(args...) +#define hres_event_cnt(args...) +#define hres_event_start(args...) +#define hres_event_end(args...) +#define hres_event(args...) +#define hres_evlog_enable(args...) +#define hres_evlog_disable(args...) +#define hres_evlog_print(args...) +#define hres_evlog_reset(args...) + +#endif + + +#endif // __HRES_COUNTER_INCLUDED__ + + +