| /* Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| */ |
| |
| /* |
| * I2C controller logging/Debugfs for QTI MSM platforms |
| */ |
| |
| #include <linux/device.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/debugfs.h> |
| #include <linux/msm-sps.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/platform_device.h> |
| #include <linux/i2c.h> |
| #include <linux/i2c/i2c-msm-v2.h> |
| |
| #ifdef CONFIG_DEBUG_FS |
| |
| enum i2c_msm_dbgfs_file_type { |
| I2C_MSM_DFS_U8, |
| I2C_MSM_DFS_U32, |
| I2C_MSM_DFS_FILE, |
| }; |
| |
| /* |
| * i2c_msm_dbgfs_file: entry in a table of debugfs files |
| * |
| * @name debugfs file name |
| * @mode file permissions |
| * @fops used when type == I2C_MSM_DFS_FILE |
| * @value_ptr used when type != I2C_MSM_DFS_FILE |
| */ |
| struct i2c_msm_dbgfs_file { |
| const char *name; |
| const umode_t mode; |
| enum i2c_msm_dbgfs_file_type type; |
| const struct file_operations *fops; |
| u32 *value_ptr; |
| }; |
| |
| static const umode_t I2C_MSM_DFS_MD_R = S_IRUSR | S_IRGRP; |
| static const umode_t I2C_MSM_DFS_MD_W = S_IWUSR | S_IWGRP; |
| static const umode_t I2C_MSM_DFS_MD_RW = S_IRUSR | S_IRGRP | |
| S_IWUSR | S_IWGRP; |
| |
| void i2c_msm_dbgfs_create(struct i2c_msm_ctrl *ctrl, |
| struct i2c_msm_dbgfs_file *itr) |
| { |
| struct dentry *file; |
| |
| ctrl->dbgfs.root = debugfs_create_dir(dev_name(ctrl->dev), NULL); |
| if (!ctrl->dbgfs.root) { |
| dev_err(ctrl->dev, "error on creating debugfs root\n"); |
| return; |
| } |
| |
| for (; itr->name; ++itr) { |
| switch (itr->type) { |
| case I2C_MSM_DFS_FILE: |
| file = debugfs_create_file(itr->name, |
| itr->mode, |
| ctrl->dbgfs.root, |
| ctrl, itr->fops); |
| break; |
| case I2C_MSM_DFS_U8: |
| file = debugfs_create_u8(itr->name, |
| itr->mode, |
| ctrl->dbgfs.root, |
| (u8 *) itr->value_ptr); |
| break; |
| default: /* I2C_MSM_DFS_U32 */ |
| file = debugfs_create_u32(itr->name, |
| itr->mode, |
| ctrl->dbgfs.root, |
| (u32 *) itr->value_ptr); |
| break; |
| } |
| if (!file) |
| dev_err(ctrl->dev, |
| "error on creating debugfs entry:%s\n", |
| itr->name); |
| } |
| } |
| |
| void i2c_msm_dbgfs_init(struct i2c_msm_ctrl *ctrl) |
| { |
| struct i2c_msm_dbgfs_file i2c_msm_dbgfs_map[] = { |
| {"dbg-lvl", I2C_MSM_DFS_MD_RW, I2C_MSM_DFS_U8, |
| NULL, &ctrl->dbgfs.dbg_lvl}, |
| {"xfer-force-mode", I2C_MSM_DFS_MD_RW, I2C_MSM_DFS_U8, |
| NULL, &ctrl->dbgfs.force_xfer_mode}, |
| {NULL, 0, 0, NULL , NULL}, /* null terminator */ |
| }; |
| return i2c_msm_dbgfs_create(ctrl, i2c_msm_dbgfs_map); |
| } |
| EXPORT_SYMBOL(i2c_msm_dbgfs_init); |
| |
| void i2c_msm_dbgfs_teardown(struct i2c_msm_ctrl *ctrl) |
| { |
| debugfs_remove_recursive(ctrl->dbgfs.root); |
| } |
| EXPORT_SYMBOL(i2c_msm_dbgfs_teardown); |
| |
| #else |
| void i2c_msm_dbgfs_init(struct i2c_msm_ctrl *ctrl) {} |
| EXPORT_SYMBOL(i2c_msm_dbgfs_init); |
| |
| void i2c_msm_dbgfs_teardown(struct i2c_msm_ctrl *ctrl) {} |
| EXPORT_SYMBOL(i2c_msm_dbgfs_teardown); |
| #endif |
| |
| /* |
| * i2c_msm_dbg_tag_byte: accessor for tag as four bytes array |
| */ |
| static u8 *i2c_msm_dbg_tag_byte(struct i2c_msm_tag *tag, int byte_n) |
| { |
| return ((u8 *)tag) + byte_n; |
| } |
| static const char * const i2c_msm_fifo_sz_str_tbl[] |
| = {"x2 blk sz", "x4 blk sz" , "x8 blk sz", "x16 blk sz"}; |
| static const char * const i2c_msm_fifo_block_sz_str_tbl[] |
| = {"16", "16" , "32", "0"}; |
| |
| /* string table for qup_io_modes register */ |
| static const char * const i2c_msm_qup_mode_str_tbl[] = { |
| "FIFO", "Block", "Reserved", "DMA", |
| }; |
| |
| static const char * const i2c_msm_mini_core_str_tbl[] = { |
| "null", "SPI", "I2C", "reserved", |
| }; |
| /* |
| * i2c_msm_qup_reg_fld: a register field descriptor |
| * @name field name |
| * @to_str_tbl when not null, used to interpret the bits value. The bits value |
| * is the table entry number. |
| */ |
| struct i2c_msm_qup_reg_fld { |
| const char * const name; |
| int bit_idx; |
| int n_bits; |
| const char * const *to_str_tbl; |
| }; |
| |
| static const char * const i2c_msm_reg_qup_state_to_str[] = { |
| "Reset", "Run", "Clear", "Pause" |
| }; |
| |
| /* QUP_STATE register fields */ |
| static struct i2c_msm_qup_reg_fld i2c_msm_qup_state_fields_map[] = { |
| { "STATE", 0, 2, i2c_msm_reg_qup_state_to_str}, |
| { "VALID", 2, 1}, |
| { "MAST_GEN", 4, 1}, |
| { "WAIT_EOT", 5, 1}, |
| { "FLUSH", 6, 1}, |
| { NULL, 0, 1}, |
| }; |
| |
| /* QUP_CONFIG register fields */ |
| static struct i2c_msm_qup_reg_fld i2c_msm_qup_config_fields_map[] = { |
| { "N", 0, 5}, |
| { "MINI_CORE", 8, 2, i2c_msm_mini_core_str_tbl}, |
| { "NO_OUTPUT", 6, 1}, |
| { "NO_INPUT", 7, 1}, |
| { "EN_EXT_OUT", 16, 1}, |
| { NULL, 0, 1}, |
| }; |
| |
| /* QUP_OPERATIONAL register fields */ |
| static struct i2c_msm_qup_reg_fld i2c_msm_qup_op_fields_map[] = { |
| { "OUT_FF_N_EMPTY", 4, 1}, |
| { "IN_FF_N_EMPTY", 5, 1}, |
| { "OUT_FF_FUL", 6, 1}, |
| { "IN_FF_FUL", 7, 1}, |
| { "OUT_SRV_FLG", 8, 1}, |
| { "IN_SRV_FLG", 9, 1}, |
| { "MX_OUT_DN", 10, 1}, |
| { "MX_IN_DN", 11, 1}, |
| { "OUT_BLK_WR", 12, 1}, |
| { "IN_BLK_RD", 13, 1}, |
| { "DONE_TGL", 14, 1}, |
| { "NWD", 15, 1}, |
| { NULL, 0, 1}, |
| }; |
| |
| /* QUP_I2C_STATUS (a.k.a I2C_MASTER_STATUS) register fields */ |
| static struct i2c_msm_qup_reg_fld i2c_msm_qup_i2c_stat_fields_map[] = { |
| { "BUS_ERR", 2, 1}, |
| { "NACK", 3, 1}, |
| { "ARB_LOST", 4, 1}, |
| { "INVLD_WR", 5, 1}, |
| { "FAIL", 6, 2}, |
| { "BUS_ACTV", 8, 1}, |
| { "BUS_MSTR", 9, 1}, |
| { "DAT_STATE", 10, 3}, |
| { "CLK_STATE", 13, 3}, |
| { "O_FSM_STAT", 16, 3}, |
| { "I_FSM_STAT", 19, 3}, |
| { "INVLD_TAG", 23, 1}, |
| { "INVLD_RD_ADDR", 24, 1}, |
| { "INVLD_RD_SEQ", 25, 1}, |
| { "SDA", 26, 1}, |
| { "SCL", 27, 1}, |
| { NULL, 0, 1}, |
| }; |
| |
| /* QUP_ERROR_FLAGS register fields */ |
| static struct i2c_msm_qup_reg_fld i2c_msm_qup_err_flags_fields_map[] = { |
| { "IN_OVR_RUN", 2, 1}, |
| { "OUT_UNDR_RUN", 3, 1}, |
| { "IN_UNDR_RUN", 4, 1}, |
| { "OUT_OVR_RUN", 5, 1}, |
| { NULL, 0, 1}, |
| }; |
| |
| /* QUP_OPERATIONAL_MASK register fields */ |
| static struct i2c_msm_qup_reg_fld i2c_msm_qup_op_mask_fields_map[] = { |
| { "OUT_SRVC_MASK", 8, 1}, |
| { "IN_SRVC_MASK", 9, 1}, |
| { NULL, 0, 1}, |
| }; |
| |
| /* QUP_I2C_MASTER_CLK_CTL register fields */ |
| static struct i2c_msm_qup_reg_fld i2c_msm_qup_master_clk_fields_map[] = { |
| { "FS_DIV", 0, 8}, |
| { "HS_DIV", 8, 3}, |
| { "HI_TM_DIV", 16, 8}, |
| { "SCL_NS_RJCT", 24, 2}, |
| { "SDA_NS_RJCT", 26, 2}, |
| { "SCL_EXT_FRC_L", 28, 1}, |
| { NULL, 0, 1}, |
| }; |
| |
| static const char * const i2c_msm_dbg_tag_val_str_tbl[] = { |
| "NOP_WAIT", /* 0x80 */ |
| "START", /* 0x81 */ |
| "DATAWRITE", /* 0x82 */ |
| "DATAWRT_and_STOP", /* 0x83 */ |
| NULL, /* 0x84 */ |
| "DATAREAD", /* 0x85 */ |
| "DATARD_and_NACK", /* 0x86 */ |
| "DATARD_and_STOP", /* 0x87 */ |
| "STOP_TAG", /* 0x88 */ |
| NULL, /* 0x89 */ |
| NULL, /* 0x8A */ |
| NULL, /* 0x8B */ |
| NULL, /* 0x8C */ |
| NULL, /* 0x8D */ |
| NULL, /* 0x8E */ |
| NULL, /* 0x8F */ |
| "NOP_MARK", /* 0x90 */ |
| "NOP_ID", /* 0x91 */ |
| "TIME_STAMP", /* 0x92 */ |
| "INPUT_EOT", /* 0x93 */ |
| "INPUT_EOT_FLUSH", /* 0x94 */ |
| "NOP_LOCAL", /* 0x95 */ |
| "FLUSH STOP", /* 0x96 */ |
| }; |
| |
| /* QUP_IO_MODES register fields */ |
| static struct i2c_msm_qup_reg_fld i2c_msm_qup_io_modes_map[] = { |
| { "IN_BLK_SZ", 5, 2, i2c_msm_fifo_block_sz_str_tbl}, |
| { "IN_FF_SZ", 7, 3, i2c_msm_fifo_sz_str_tbl}, |
| { "OUT_BLK_SZ", 0, 2, i2c_msm_fifo_block_sz_str_tbl}, |
| { "OUT_FF_SZ", 2, 3, i2c_msm_fifo_sz_str_tbl}, |
| { "UNPACK", 14, 1}, |
| { "PACK", 15, 1}, |
| { "INP_MOD", 12, 2, i2c_msm_qup_mode_str_tbl}, |
| { "OUT_MOD", 10, 2, i2c_msm_qup_mode_str_tbl}, |
| { NULL, 0, 1}, |
| }; |
| |
| /* |
| * i2c_msm_qup_reg_dump: desc fmt of reg to dump via i2c_msm_dbg_qup_reg_dump() |
| * |
| * @offset the register's offset in the QUP |
| * @name name to dump before value |
| * @field_map when set i2c_msm_dbg_qup_reg_flds_to_str() is used. Otherwise |
| * if val_to_str_func() is set, then it is used. When both are NULL |
| * none is used. Only the register's value is dumped. |
| */ |
| struct i2c_msm_qup_reg_dump { |
| u32 offset; |
| const char *name; |
| struct i2c_msm_qup_reg_fld *field_map; |
| }; |
| |
| static const struct i2c_msm_qup_reg_dump i2c_msm_qup_reg_dump_map[] = { |
| {QUP_CONFIG, "QUP_CONFIG", i2c_msm_qup_config_fields_map }, |
| {QUP_STATE, "QUP_STATE", i2c_msm_qup_state_fields_map }, |
| {QUP_IO_MODES, "QUP_IO_MDS", i2c_msm_qup_io_modes_map }, |
| {QUP_ERROR_FLAGS, "QUP_ERR_FLGS", i2c_msm_qup_err_flags_fields_map }, |
| {QUP_OPERATIONAL, "QUP_OP", i2c_msm_qup_op_fields_map }, |
| {QUP_OPERATIONAL_MASK, "QUP_OP_MASK", i2c_msm_qup_op_mask_fields_map }, |
| {QUP_I2C_STATUS, "QUP_I2C_STAT", i2c_msm_qup_i2c_stat_fields_map }, |
| {QUP_I2C_MASTER_CLK_CTL, "QUP_MSTR_CLK", i2c_msm_qup_master_clk_fields_map}, |
| {QUP_IN_DEBUG, "QUP_IN_DBG" }, |
| {QUP_OUT_DEBUG, "QUP_OUT_DBG" }, |
| {QUP_IN_FIFO_CNT, "QUP_IN_CNT" }, |
| {QUP_OUT_FIFO_CNT, "QUP_OUT_CNT" }, |
| {QUP_MX_READ_COUNT, "MX_RD_CNT" }, |
| {QUP_MX_WRITE_COUNT, "MX_WR_CNT" }, |
| {QUP_MX_INPUT_COUNT, "MX_IN_CNT" }, |
| {QUP_MX_OUTPUT_COUNT, "MX_OUT_CNT" }, |
| {0, NULL }, |
| }; |
| |
| static const char *i2c_msm_dbg_tag_val_to_str(u8 tag_val) |
| { |
| if ((tag_val < 0x80) || (tag_val > 0x96) || (tag_val == 0x84) || |
| ((tag_val > 0x88) && (0x90 > tag_val))) |
| return "Invalid_tag"; |
| |
| return i2c_msm_dbg_tag_val_str_tbl[tag_val - 0x80]; |
| } |
| |
| /* |
| * i2c_msm_dbg_qup_reg_flds_to_str: format register's fields using a field map |
| * |
| * @fld an array of fields mapping bits of val to fields/flags values |
| * @val the register's value |
| * @buf buffer to format the strings into |
| * @len buf's len |
| */ |
| static const char *i2c_msm_dbg_qup_reg_flds_to_str( |
| u32 val, char *buf, int len, const struct i2c_msm_qup_reg_fld *fld) |
| { |
| char *ptr = buf; |
| int str_len; |
| int str_len_sum = 0; |
| int rem_len = len; |
| u32 field_val; |
| for (; fld->name && (rem_len > 0); ++fld) { |
| if (fld->n_bits == 1) { |
| field_val = BIT_IS_SET(val, fld->bit_idx); |
| /* |
| * Only dump interesting flags (skip flags who's value |
| * is zero). |
| */ |
| if (!field_val) |
| continue; |
| |
| str_len = snprintf(ptr, rem_len, "%s ", fld->name); |
| } else { |
| field_val = BITS_AT(val, fld->bit_idx, fld->n_bits); |
| |
| /* |
| * Only dump interesting fields (skip fields who's value |
| * is zero). |
| */ |
| if (!field_val) |
| continue; |
| |
| if (fld->to_str_tbl) |
| str_len = snprintf(ptr, rem_len, "%s:%s ", |
| fld->name, fld->to_str_tbl[field_val]); |
| else |
| str_len = snprintf(ptr, rem_len, "%s:0x%x ", |
| fld->name, field_val); |
| } |
| |
| if (str_len > rem_len) { |
| pr_err("%s insufficient buffer space\n", __func__); |
| /* snprintf does not guarantee NULL terminator */ |
| buf[len - 1] = 0; |
| return buf; |
| } |
| |
| rem_len -= str_len; |
| ptr += str_len; |
| str_len_sum += str_len; |
| } |
| |
| /* snprintf does not guarantee NULL terminator */ |
| buf[len - 1] = 0; |
| return buf; |
| } |
| |
| const char *i2c_msm_dbg_tag_to_str(const struct i2c_msm_tag *tag, |
| char *buf, size_t buf_len) |
| { |
| /* cast const away. t is read-only here */ |
| struct i2c_msm_tag *t = (struct i2c_msm_tag *) tag; |
| switch (tag->len) { |
| case 6: |
| snprintf(buf, buf_len, "val:0x%012llx %s:0x%x %s:0x%x %s:%d", |
| tag->val, |
| i2c_msm_dbg_tag_val_to_str(*i2c_msm_dbg_tag_byte(t, 0)), |
| *i2c_msm_dbg_tag_byte(t, 1), |
| i2c_msm_dbg_tag_val_to_str(*i2c_msm_dbg_tag_byte(t, 2)), |
| *i2c_msm_dbg_tag_byte(t, 3), |
| i2c_msm_dbg_tag_val_to_str(*i2c_msm_dbg_tag_byte(t, 4)), |
| *i2c_msm_dbg_tag_byte(t, 5)); |
| break; |
| case 4: |
| snprintf(buf, buf_len, "val:0x%08llx %s:0x%x %s:%d", |
| (tag->val & 0xffffffff), |
| i2c_msm_dbg_tag_val_to_str(*i2c_msm_dbg_tag_byte(t, 0)), |
| *i2c_msm_dbg_tag_byte(t, 1), |
| i2c_msm_dbg_tag_val_to_str(*i2c_msm_dbg_tag_byte(t, 2)), |
| *i2c_msm_dbg_tag_byte(t, 3)); |
| break; |
| default: /* 2 bytes tag */ |
| snprintf(buf, buf_len, "val:0x%04llx %s:%d", |
| (tag->val & 0xffff), |
| i2c_msm_dbg_tag_val_to_str(*i2c_msm_dbg_tag_byte(t, 0)), |
| *i2c_msm_dbg_tag_byte(t, 1)); |
| } |
| |
| return buf; |
| } |
| EXPORT_SYMBOL(i2c_msm_dbg_tag_to_str); |
| |
| const char * |
| i2c_msm_dbg_dma_tag_to_str(const struct i2c_msm_dma_tag *dma_tag, char *buf, |
| size_t buf_len) |
| { |
| const char *ret; |
| u32 *val; |
| struct i2c_msm_tag tag; |
| |
| val = phys_to_virt(dma_tag->buf); |
| if (!val) { |
| pr_err("Failed phys_to_virt(0x%llx)", (u64) dma_tag->buf); |
| return "Error"; |
| } |
| |
| tag = (struct i2c_msm_tag) { |
| .val = *val, |
| .len = dma_tag->len, |
| }; |
| |
| ret = i2c_msm_dbg_tag_to_str(&tag, buf, buf_len); |
| return ret; |
| } |
| EXPORT_SYMBOL(i2c_msm_dbg_dma_tag_to_str); |
| |
| /* |
| * see: struct i2c_msm_qup_reg_dump for more |
| */ |
| int i2c_msm_dbg_qup_reg_dump(struct i2c_msm_ctrl *ctrl) |
| { |
| u32 val; |
| char buf[I2C_MSM_REG_2_STR_BUF_SZ]; |
| void __iomem *base = ctrl->rsrcs.base; |
| const struct i2c_msm_qup_reg_dump *itr = i2c_msm_qup_reg_dump_map; |
| |
| for (; itr->name; ++itr) { |
| val = readl_relaxed(base + itr->offset); |
| buf[0] = 0; |
| if (itr->field_map) |
| i2c_msm_dbg_qup_reg_flds_to_str(val, buf, sizeof(buf), |
| itr->field_map); |
| dev_err(ctrl->dev, "%-12s:0x%08x %s\n", itr->name, val, buf); |
| }; |
| return 0; |
| } |
| EXPORT_SYMBOL(i2c_msm_dbg_qup_reg_dump); |
| |
| typedef void (*i2c_msm_prof_dump_func_func_t)(struct i2c_msm_ctrl *, |
| struct i2c_msm_prof_event *, size_t msec, size_t usec); |
| |
| /* |
| * i2c_msm_prof_evnt_add: pushes event into end of event array |
| * |
| * @dump_now log a copy immediately to kernel log |
| * |
| * Implementation of i2c_msm_prof_evnt_add(). When array overflows, the last |
| * entry is overwritten as many times as it overflows. |
| */ |
| void i2c_msm_prof_evnt_add(struct i2c_msm_ctrl *ctrl, |
| enum msm_i2_debug_level dbg_level, |
| enum i2c_msm_prof_evnt_type event_type, |
| u64 data0, u32 data1, u32 data2) |
| { |
| struct i2c_msm_xfer *xfer = &ctrl->xfer; |
| struct i2c_msm_prof_event *event; |
| int idx; |
| |
| if (ctrl->dbgfs.dbg_lvl < dbg_level) |
| return; |
| |
| atomic_add_unless(&xfer->event_cnt, 1, I2C_MSM_PROF_MAX_EVNTS - 1); |
| idx = atomic_read(&xfer->event_cnt) - 1; |
| if (idx > (I2C_MSM_PROF_MAX_EVNTS - 1)) |
| dev_err(ctrl->dev, "error event index:%d max:%d\n", |
| idx, I2C_MSM_PROF_MAX_EVNTS); |
| event = &xfer->event[idx]; |
| |
| getnstimeofday(&event->time); |
| event->dump_func_id = event_type; |
| event->data0 = data0; |
| event->data1 = data1; |
| event->data2 = data2; |
| } |
| EXPORT_SYMBOL(i2c_msm_prof_evnt_add); |
| |
| void i2c_msm_prof_dump_xfer_beg(struct i2c_msm_ctrl *ctrl, |
| struct i2c_msm_prof_event *event, size_t msec, size_t usec) |
| { |
| dev_info(ctrl->dev, |
| "-->.%03zums XFER_BEG msg_cnt:%llx addr:0x%x\n", |
| usec, event->data0, event->data1); |
| } |
| |
| void i2c_msm_prof_dump_actv_end(struct i2c_msm_ctrl *ctrl, |
| struct i2c_msm_prof_event *event, size_t msec, size_t usec) |
| { |
| dev_info(ctrl->dev, |
| "%3zu.%03zums ACTV_END ret:%lld jiffies_left:%u/%u read_cnt:%u\n", |
| msec, usec, event->data0, event->data1, |
| I2C_MSM_MAX_POLL_MSEC, event->data2); |
| } |
| |
| void i2c_msm_prof_dump_dma_flsh(struct i2c_msm_ctrl *ctrl, |
| struct i2c_msm_prof_event *event, size_t msec, size_t usec) |
| { |
| dev_info(ctrl->dev, "%3zu.%03zums DMA_FLSH\n", msec, usec); |
| } |
| |
| void i2c_msm_prof_dump_pip_dscn(struct i2c_msm_ctrl *ctrl, |
| struct i2c_msm_prof_event *event, size_t msec, size_t usec) |
| { |
| struct i2c_msm_dma_chan *chan = |
| (struct i2c_msm_dma_chan *) ((ulong) event->data0); |
| int ret = event->data1; |
| dev_info(ctrl->dev, |
| "%3zu.%03zums PIP_DCNCT sps_disconnect(hndl:0x%p %s):%d\n", |
| msec, usec, chan->dma_chan, chan->name, ret); |
| } |
| |
| void i2c_msm_prof_dump_pip_cnct(struct i2c_msm_ctrl *ctrl, |
| struct i2c_msm_prof_event *event, size_t msec, size_t usec) |
| { |
| struct i2c_msm_dma_chan *chan = |
| (struct i2c_msm_dma_chan *) ((ulong) event->data0); |
| int ret = event->data1; |
| dev_info(ctrl->dev, |
| "%3zu.%03zums PIP_CNCT sps_connect(hndl:0x%p %s):%d\n", |
| msec, usec, chan->dma_chan, chan->name, ret); |
| } |
| |
| void i2c_msm_prof_reset(struct i2c_msm_ctrl *ctrl, |
| struct i2c_msm_prof_event *event, size_t msec, size_t usec) |
| { |
| dev_info(ctrl->dev, "%3zu.%03zums QUP_RSET\n", msec, usec); |
| } |
| |
| /* string table for enum i2c_msm_err_bit_field */ |
| const char * const i2c_msm_err_str_tbl[] = { |
| "NONE", "NACK", "ARB_LOST" , "ARB_LOST + NACK", "BUS_ERR", |
| "BUS_ERR + NACK", "BUS_ERR + ARB_LOST", "BUS_ERR + ARB_LOST + NACK", |
| "TIMEOUT", "TIMEOUT + NACK", "TIMEOUT + ARB_LOST", |
| "TIMEOUT + ARB_LOST + NACK", "TIMEOUT + BUS_ERR", |
| "TIMEOUT + BUS_ERR + NACK" , "TIMEOUT + BUS_ERR + ARB_LOST", |
| "TIMEOUT + BUS_ERR + ARB_LOST + NACK", |
| }; |
| |
| void i2c_msm_prof_dump_xfer_end(struct i2c_msm_ctrl *ctrl, |
| struct i2c_msm_prof_event *event, size_t msec, size_t usec) |
| { |
| int ret = event->data0; |
| int err = event->data1; |
| int bc = ctrl->xfer.rx_cnt + ctrl->xfer.rx_ovrhd_cnt + |
| ctrl->xfer.tx_cnt + ctrl->xfer.tx_ovrhd_cnt; |
| int bc_sec = (bc * 1000000) / (msec * 1000 + usec); |
| const char *status = (!err && (ret == ctrl->xfer.msg_cnt)) ? |
| "OK" : "FAIL"; |
| dev_info(ctrl->dev, |
| "%3zu.%03zums XFER_END ret:%d err:[%s] msgs_sent:%d BC:%d B/sec:%d i2c-stts:%s\n" , |
| msec, usec, ret, i2c_msm_err_str_tbl[err], event->data2, |
| bc, bc_sec, status); |
| } |
| |
| void i2c_msm_prof_dump_irq_begn(struct i2c_msm_ctrl *ctrl, |
| struct i2c_msm_prof_event *event, size_t msec, size_t usec) |
| { |
| dev_info(ctrl->dev, "%3zu.%03zums IRQ_BEG irq:%lld\n", |
| msec, usec, event->data0); |
| } |
| |
| void i2c_msm_prof_dump_irq_end(struct i2c_msm_ctrl *ctrl, |
| struct i2c_msm_prof_event *event, size_t msec, size_t usec) |
| { |
| char str[I2C_MSM_REG_2_STR_BUF_SZ]; |
| u32 mstr_stts = event->data0; |
| u32 qup_oper = event->data1; |
| u32 err_flgs = event->data2; |
| dev_info(ctrl->dev, |
| "%3zu.%03zums IRQ_END MSTR_STTS:0x%x QUP_OPER:0x%x ERR_FLGS:0x%x\n", |
| msec, usec, mstr_stts, qup_oper, err_flgs); |
| |
| /* |
| * Dump fields and flags only of registers with interesting info |
| * (i.e. errors). |
| */ |
| /* register I2C_MASTER_STATUS */ |
| if (mstr_stts & QUP_MSTR_STTS_ERR_MASK) { |
| i2c_msm_dbg_qup_reg_flds_to_str( |
| mstr_stts, str, sizeof(str), |
| i2c_msm_qup_i2c_stat_fields_map); |
| |
| dev_info(ctrl->dev, " |->MSTR_STTS:0x%llx %s\n", |
| event->data0, str); |
| } |
| /* register QUP_OPERATIONAL */ |
| if (qup_oper & |
| (QUP_OUTPUT_SERVICE_FLAG | QUP_INPUT_SERVICE_FLAG)) { |
| |
| i2c_msm_dbg_qup_reg_flds_to_str( |
| qup_oper, str, sizeof(str), |
| i2c_msm_qup_op_fields_map); |
| |
| dev_info(ctrl->dev, " |-> QUP_OPER:0x%x %s\n", |
| event->data1, str); |
| } |
| /* register ERR_FLAGS */ |
| if (err_flgs) { |
| i2c_msm_dbg_qup_reg_flds_to_str( |
| err_flgs, str, sizeof(str), |
| i2c_msm_qup_err_flags_fields_map); |
| |
| dev_info(ctrl->dev, " |-> ERR_FLGS:0x%x %s\n", |
| event->data2, str); |
| } |
| } |
| |
| void i2c_msm_prof_dump_next_buf(struct i2c_msm_ctrl *ctrl, |
| struct i2c_msm_prof_event *event, size_t msec, size_t usec) |
| { |
| struct i2c_msg *msg = ctrl->xfer.msgs + event->data0; |
| dev_info(ctrl->dev, |
| "%3zu.%03zums XFER_BUF msg[%lld] pos:%d adr:0x%x len:%d is_rx:0x%x last:0x%x\n", |
| msec, usec, event->data0, event->data1, msg->addr, msg->len, |
| (msg->flags & I2C_M_RD), |
| event->data0 == (ctrl->xfer.msg_cnt - 1)); |
| |
| } |
| |
| void i2c_msm_prof_dump_scan_sum(struct i2c_msm_ctrl *ctrl, |
| struct i2c_msm_prof_event *event, size_t msec, size_t usec) |
| { |
| u32 bc_rx = (event->data0 & 0xff); |
| u32 bc_rx_ovrhd = (event->data0 >> 16); |
| u32 bc_tx = (event->data1 & 0xff); |
| u32 bc_tx_ovrhd = (event->data1 >> 16); |
| u32 timeout = (event->data2 & 0xfff); |
| u32 mode = (event->data2 >> 24); |
| u32 bc = bc_rx + bc_rx_ovrhd + bc_tx + bc_tx_ovrhd; |
| dev_info(ctrl->dev, |
| "%3zu.%03zums SCN_SMRY BC:%u rx:%u+ovrhd:%u tx:%u+ovrhd:%u timeout:%umsec mode:%s\n", |
| msec, usec, bc, bc_rx, bc_rx_ovrhd, bc_tx, bc_tx_ovrhd, |
| jiffies_to_msecs(timeout), i2c_msm_mode_str_tbl[mode]); |
| } |
| |
| void i2c_msm_prof_dump_cmplt_ok(struct i2c_msm_ctrl *ctrl, |
| struct i2c_msm_prof_event *event, size_t msec, size_t usec) |
| { |
| dev_info(ctrl->dev, |
| "%3zu.%03zums DONE_OK timeout-used:%umsec time_left:%umsec\n", |
| msec, usec, jiffies_to_msecs(event->data0), |
| jiffies_to_msecs(event->data1)); |
| } |
| |
| void i2c_msm_prof_dump_cmplt_fl(struct i2c_msm_ctrl *ctrl, |
| struct i2c_msm_prof_event *event, size_t msec, size_t usec) |
| { |
| dev_info(ctrl->dev, |
| "%3zu.%03zums TIMEOUT-error timeout-used:%umsec. Check GPIOs configuration\n", |
| msec, usec, jiffies_to_msecs(event->data0)); |
| } |
| |
| void i2c_msm_prof_dump_vlid_end(struct i2c_msm_ctrl *ctrl, |
| struct i2c_msm_prof_event *event, size_t msec, size_t usec) |
| { |
| int ret = (int)(event->data0 & 0xff); |
| enum i2c_msm_qup_state state = ((event->data0 << 16) & 0xf); |
| u32 status = event->data2; |
| |
| dev_info(ctrl->dev, |
| "%3zu.%03zums SET_STTE set:%s ret:%d rd_cnt:%u reg_val:0x%x vld:%d\n", |
| msec, usec, i2c_msm_reg_qup_state_to_str[state], ret, |
| event->data1, status, BIT_IS_SET(status, 2)); |
| } |
| /* match the corresponding prof event enum to the prof function declaration */ |
| static i2c_msm_prof_dump_func_func_t event_dump_func_tbl[] = { |
| [I2C_MSM_VALID_END] = i2c_msm_prof_dump_vlid_end, |
| [I2C_MSM_PIP_DSCN] = i2c_msm_prof_dump_pip_dscn, |
| [I2C_MSM_PIP_CNCT] = i2c_msm_prof_dump_pip_cnct, |
| [I2C_MSM_ACTV_END] = i2c_msm_prof_dump_actv_end, |
| [I2C_MSM_IRQ_BGN] = i2c_msm_prof_dump_irq_begn, |
| [I2C_MSM_IRQ_END] = i2c_msm_prof_dump_irq_end, |
| [I2C_MSM_XFER_BEG] = i2c_msm_prof_dump_xfer_beg, |
| [I2C_MSM_XFER_END] = i2c_msm_prof_dump_xfer_end, |
| [I2C_MSM_SCAN_SUM] = i2c_msm_prof_dump_scan_sum, |
| [I2C_MSM_NEXT_BUF] = i2c_msm_prof_dump_next_buf, |
| [I2C_MSM_COMPLT_OK] = i2c_msm_prof_dump_cmplt_ok, |
| [I2C_MSM_COMPLT_FL] = i2c_msm_prof_dump_cmplt_fl, |
| [I2C_MSM_PROF_RESET] = i2c_msm_prof_reset, |
| }; |
| |
| /* |
| * i2c_msm_prof_evnt_dump: post processing, msg formatting and dumping of events |
| */ |
| void i2c_msm_prof_evnt_dump(struct i2c_msm_ctrl *ctrl) |
| { |
| size_t cnt = atomic_read(&ctrl->xfer.event_cnt); |
| struct i2c_msm_prof_event *event = ctrl->xfer.event; |
| struct timespec time0 = event->time; |
| struct timespec time_diff; |
| size_t diff_usec; |
| size_t diff_msec; |
| i2c_msm_prof_dump_func_func_t func; |
| |
| for (; cnt; --cnt, ++event) { |
| time_diff = timespec_sub(event->time, time0); |
| diff_usec = time_diff.tv_sec * USEC_PER_SEC + |
| time_diff.tv_nsec / NSEC_PER_USEC; |
| diff_msec = diff_usec / USEC_PER_MSEC; |
| diff_usec -= diff_msec * USEC_PER_MSEC; |
| |
| func = event_dump_func_tbl[event->dump_func_id]; |
| func(ctrl, event, diff_msec, diff_usec); |
| } |
| } |
| EXPORT_SYMBOL(i2c_msm_prof_evnt_dump); |