/* 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TSPP2_MODULUS_OP(val, mod) ((val) & ((mod) - 1)) /* General definitions. Note we're reserving one batch. */ #define TSPP2_NUM_ALL_INPUTS (TSPP2_NUM_TSIF_INPUTS + TSPP2_NUM_MEM_INPUTS) #define TSPP2_NUM_CONTEXTS 128 #define TSPP2_NUM_AVAIL_CONTEXTS 127 #define TSPP2_NUM_HW_FILTERS 128 #define TSPP2_NUM_BATCHES 15 #define TSPP2_FILTERS_PER_BATCH 8 #define TSPP2_NUM_AVAIL_FILTERS (TSPP2_NUM_HW_FILTERS - TSPP2_FILTERS_PER_BATCH) #define TSPP2_NUM_KEYTABLES 32 #define TSPP2_TSIF_DEF_TIME_LIMIT 15000 /* Number of tsif-ref-clock ticks */ #define TSPP2_NUM_EVENT_WORK_ELEMENTS 256 /* * Based on the hardware programming guide, HW requires we wait for up to 2ms * before closing the pipes used by the filter. * This is required to avoid unexpected pipe reset interrupts. */ #define TSPP2_HW_DELAY_USEC 2000 /* * Default source configuration: * Sync byte 0x47, check sync byte, * Do not monitor scrambling bits, * Discard packets with invalid AF, * Do not assume duplicates, * Do not ignore discontinuity indicator, * Check continuity of TS packets. */ #define TSPP2_DEFAULT_SRC_CONFIG 0x47801E49 /* * Default memory source configuration: * Use 16 batches, * Attach last batch to each memory source. */ #define TSPP2_DEFAULT_MEM_SRC_CONFIG 0x80000010 /* Bypass VBIF/IOMMU for debug and bring-up purposes */ static int tspp2_iommu_bypass; module_param(tspp2_iommu_bypass, int, S_IRUGO); /* Enable Invalid Adaptation Field control bits event */ static int tspp2_en_invalid_af_ctrl; module_param(tspp2_en_invalid_af_ctrl, int, S_IRUGO | S_IWUSR); /* Enable Invalid Adaptation Field length event */ static int tspp2_en_invalid_af_length; module_param(tspp2_en_invalid_af_length, int, S_IRUGO | S_IWUSR); /* Enable PES No Sync event */ static int tspp2_en_pes_no_sync; module_param(tspp2_en_pes_no_sync, int, S_IRUGO | S_IWUSR); /** * enum tspp2_operation_opcode - TSPP2 Operation opcode for TSPP2_OPCODE */ enum tspp2_operation_opcode { TSPP2_OPCODE_PES_ANALYSIS = 0x03, TSPP2_OPCODE_RAW_TRANSMIT = 0x07, TSPP2_OPCODE_PES_TRANSMIT = 0x00, TSPP2_OPCODE_PCR_EXTRACTION = 0x05, TSPP2_OPCODE_CIPHER = 0x01, TSPP2_OPCODE_INDEXING = 0x09, TSPP2_OPCODE_COPY_PACKET = 0x0B, TSPP2_OPCODE_EXIT = 0x0F }; /* TSIF Register definitions: */ #define TSPP2_TSIF_STS_CTL (0x0000) #define TSPP2_TSIF_TIME_LIMIT (0x0004) #define TSPP2_TSIF_CLK_REF (0x0008) #define TSPP2_TSIF_LPBK_FLAGS (0x000C) #define TSPP2_TSIF_LPBK_DATA (0x0010) #define TSPP2_TSIF_DATA_PORT (0x0100) /* Bits for TSPP2_TSIF_STS_CTL register */ #define TSIF_STS_CTL_PKT_WRITE_ERR BIT(30) #define TSIF_STS_CTL_PKT_READ_ERR BIT(29) #define TSIF_STS_CTL_EN_IRQ BIT(28) #define TSIF_STS_CTL_PACK_AVAIL BIT(27) #define TSIF_STS_CTL_1ST_PACKET BIT(26) #define TSIF_STS_CTL_OVERFLOW BIT(25) #define TSIF_STS_CTL_LOST_SYNC BIT(24) #define TSIF_STS_CTL_TIMEOUT BIT(23) #define TSIF_STS_CTL_INV_SYNC BIT(21) #define TSIF_STS_CTL_INV_NULL BIT(20) #define TSIF_STS_CTL_INV_ERROR BIT(19) #define TSIF_STS_CTL_INV_ENABLE BIT(18) #define TSIF_STS_CTL_INV_DATA BIT(17) #define TSIF_STS_CTL_INV_CLOCK BIT(16) #define TSIF_STS_CTL_PARALLEL BIT(14) #define TSIF_STS_CTL_EN_NULL BIT(11) #define TSIF_STS_CTL_EN_ERROR BIT(10) #define TSIF_STS_CTL_LAST_BIT BIT(9) #define TSIF_STS_CTL_EN_TIME_LIM BIT(8) #define TSIF_STS_CTL_EN_TCR BIT(7) #define TSIF_STS_CTL_TEST_MODE BIT(6) #define TSIF_STS_CTL_MODE_2 BIT(5) #define TSIF_STS_CTL_EN_DM BIT(4) #define TSIF_STS_CTL_STOP BIT(3) #define TSIF_STS_CTL_START BIT(0) /* Indexing Table Register definitions: id = 0..3, n = 0..25 */ #define TSPP2_INDEX_TABLE_PREFIX(id) (0x6000 + ((id) << 2)) #define TSPP2_INDEX_TABLE_PREFIX_MASK(id) (0x6010 + ((id) << 2)) #define TSPP2_INDEX_TABLE_PATTEREN(id, n) (0x3C00 + ((id) << 8) + \ ((n) << 3)) #define TSPP2_INDEX_TABLE_MASK(id, n) (0x3C04 + ((id) << 8) + \ ((n) << 3)) #define TSPP2_INDEX_TABLE_PARAMS(id) (0x6020 + ((id) << 2)) /* Bits for TSPP2_INDEX_TABLE_PARAMS register */ #define INDEX_TABLE_PARAMS_PREFIX_SIZE_OFFS 8 #define INDEX_TABLE_PARAMS_NUM_PATTERNS_OFFS 0 /* Source with memory input register definitions: n = 0..7 */ #define TSPP2_MEM_INPUT_SRC_CONFIG(n) (0x6040 + ((n) << 2)) /* Bits for TSPP2_MEM_INPUT_SRC_CONFIG register */ #define MEM_INPUT_SRC_CONFIG_BATCHES_OFFS 16 #define MEM_INPUT_SRC_CONFIG_INPUT_PIPE_OFFS 8 #define MEM_INPUT_SRC_CONFIG_16_BATCHES_OFFS 4 #define MEM_INPUT_SRC_CONFIG_STAMP_SUFFIX_OFFS 2 #define MEM_INPUT_SRC_CONFIG_STAMP_EN_OFFS 1 #define MEM_INPUT_SRC_CONFIG_INPUT_EN_OFFS 0 /* Source with TSIF input register definitions: n = 0..1 */ #define TSPP2_TSIF_INPUT_SRC_CONFIG(n) (0x6060 + ((n) << 2)) #define TSIF_INPUT_SRC_CONFIG_16_BATCHES_OFFS 4 /* Bits for TSPP2_TSIF_INPUT_SRC_CONFIG register */ #define TSIF_INPUT_SRC_CONFIG_BATCHES_OFFS 16 #define TSIF_INPUT_SRC_CONFIG_INPUT_EN_OFFS 0 /* Source with any input register definitions: n = 0..9 */ #define TSPP2_SRC_DEST_PIPES(n) (0x6070 + ((n) << 2)) #define TSPP2_SRC_CONFIG(n) (0x6120 + ((n) << 2)) #define TSPP2_SRC_TOTAL_TSP(n) (0x6600 + ((n) << 2)) #define TSPP2_SRC_FILTERED_OUT_TSP(n) (0x6630 + ((n) << 2)) /* Bits for TSPP2_SRC_CONFIG register */ #define SRC_CONFIG_SYNC_BYTE_OFFS 24 #define SRC_CONFIG_CHECK_SYNC_OFFS 23 #define SRC_CONFIG_SCRAMBLING_MONITOR_OFFS 13 #define SRC_CONFIG_VERIFY_PES_START_OFFS 12 #define SRC_CONFIG_SCRAMBLING3_OFFS 10 #define SRC_CONFIG_SCRAMBLING2_OFFS 8 #define SRC_CONFIG_SCRAMBLING1_OFFS 6 #define SRC_CONFIG_SCRAMBLING0_OFFS 4 #define SRC_CONFIG_DISCARD_INVALID_AF_OFFS 3 #define SRC_CONFIG_ASSUME_DUPLICATES_OFFS 2 #define SRC_CONFIG_IGNORE_DISCONT_OFFS 1 #define SRC_CONFIG_CHECK_CONT_OFFS 0 /* Context register definitions: n = 0..127 */ #define TSPP2_PES_CONTEXT0(n) (0x0000 + ((n) << 4)) #define TSPP2_PES_CONTEXT1(n) (0x0004 + ((n) << 4)) #define TSPP2_PES_CONTEXT2(n) (0x0008 + ((n) << 4)) #define TSPP2_PES_CONTEXT3(n) (0x000C + ((n) << 4)) #define TSPP2_INDEXING_CONTEXT0(n) (0x0800 + ((n) << 3)) #define TSPP2_INDEXING_CONTEXT1(n) (0x0804 + ((n) << 3)) #define TSPP2_TSP_CONTEXT(n) (0x5600 + ((n) << 2)) /* Bits for TSPP2_TSP_CONTEXT register */ #define TSP_CONTEXT_TS_HEADER_SC_OFFS 6 #define TSP_CONTEXT_PES_HEADER_SC_OFFS 8 /* Operations register definitions: f_idx = 0..127, n = 0..15 */ #define TSPP2_OPCODE(f_idx, n) (0x1000 + \ ((f_idx) * (TSPP2_MAX_OPS_PER_FILTER << 2)) + \ ((n) << 2)) /* Filter register definitions: n = 0..127 */ #define TSPP2_FILTER_ENTRY0(n) (0x5800 + ((n) << 3)) #define TSPP2_FILTER_ENTRY1(n) (0x5804 + ((n) << 3)) /* Bits for TSPP2_FILTER_ENTRY0 register */ #define FILTER_ENTRY0_PID_OFFS 0 #define FILTER_ENTRY0_MASK_OFFS 13 #define FILTER_ENTRY0_EN_OFFS 26 #define FILTER_ENTRY0_CODEC_OFFS 27 /* Bits for TSPP2_FILTER_ENTRY1 register */ #define FILTER_ENTRY1_CONTEXT_OFFS 0 /* Filter context-based counter register definitions: n = 0..127 */ #define TSPP2_FILTER_TSP_SYNC_ERROR(n) (0x4000 + ((n) << 2)) #define TSPP2_FILTER_ERRED_TSP(n) (0x4200 + ((n) << 2)) #define TSPP2_FILTER_DISCONTINUITIES(n) (0x4400 + ((n) << 2)) #define TSPP2_FILTER_SCRAMBLING_BITS_DISCARD(n) (0x4600 + ((n) << 2)) #define TSPP2_FILTER_TSP_TOTAL_NUM(n) (0x4800 + ((n) << 2)) #define TSPP2_FILTER_DISCONT_INDICATOR(n) (0x4A00 + ((n) << 2)) #define TSPP2_FILTER_TSP_NO_PAYLOAD(n) (0x4C00 + ((n) << 2)) #define TSPP2_FILTER_TSP_DUPLICATE(n) (0x4E00 + ((n) << 2)) #define TSPP2_FILTER_KEY_FETCH_FAILURE(n) (0x5000 + ((n) << 2)) #define TSPP2_FILTER_DROPPED_PCR(n) (0x5200 + ((n) << 2)) #define TSPP2_FILTER_PES_ERRORS(n) (0x5400 + ((n) << 2)) /* Pipe register definitions: n = 0..30 */ #define TSPP2_PIPE_THRESH_CONFIG(n) (0x60A0 + ((n) << 2)) #define TSPP2_PIPE_LAST_ADDRESS(n) (0x6190 + ((n) << 2)) #define TSPP2_PIPE_SECURITY 0x6150 #define TSPP2_DATA_NOT_SENT_ON_PIPE(n) (0x6660 + ((n) << 2)) /* Global register definitions: */ #define TSPP2_PCR_GLOBAL_CONFIG 0x6160 #define TSPP2_CLK_TO_PCR_TIME_UNIT 0x6170 #define TSPP2_DESC_WAIT_TIMEOUT 0x6180 #define TSPP2_GLOBAL_IRQ_STATUS 0x6300 #define TSPP2_GLOBAL_IRQ_CLEAR 0x6304 #define TSPP2_GLOBAL_IRQ_ENABLE 0x6308 #define TSPP2_KEY_NOT_READY_IRQ_STATUS 0x6310 #define TSPP2_KEY_NOT_READY_IRQ_CLEAR 0x6314 #define TSPP2_KEY_NOT_READY_IRQ_ENABLE 0x6318 #define TSPP2_UNEXPECTED_RST_IRQ_STATUS 0x6320 #define TSPP2_UNEXPECTED_RST_IRQ_CLEAR 0x6324 #define TSPP2_UNEXPECTED_RST_IRQ_ENABLE 0x6328 #define TSPP2_WRONG_PIPE_DIR_IRQ_STATUS 0x6330 #define TSPP2_WRONG_PIPE_DIR_IRQ_CLEAR 0x6334 #define TSPP2_WRONG_PIPE_DIR_IRQ_ENABLE 0x6338 #define TSPP2_QSB_RESPONSE_ERROR_IRQ_STATUS 0x6340 #define TSPP2_QSB_RESPONSE_ERROR_IRQ_CLEAR 0x6344 #define TSPP2_QSB_RESPONSE_ERROR_IRQ_ENABLE 0x6348 #define TSPP2_SRC_TOTAL_TSP_RESET 0x6710 #define TSPP2_SRC_FILTERED_OUT_TSP_RESET 0x6714 #define TSPP2_DATA_NOT_SENT_ON_PIPE_RESET 0x6718 #define TSPP2_VERSION 0x6FFC /* Bits for TSPP2_GLOBAL_IRQ_CLEAR register */ #define GLOBAL_IRQ_CLEAR_RESERVED_OFFS 4 /* Bits for TSPP2_VERSION register */ #define VERSION_MAJOR_OFFS 28 #define VERSION_MINOR_OFFS 16 #define VERSION_STEP_OFFS 0 /* Bits for TSPP2_GLOBAL_IRQ_XXX registers */ #define GLOBAL_IRQ_TSP_INVALID_AF_OFFS 0 #define GLOBAL_IRQ_TSP_INVALID_LEN_OFFS 1 #define GLOBAL_IRQ_PES_NO_SYNC_OFFS 2 #define GLOBAL_IRQ_ENCRYPT_LEVEL_ERR_OFFS 3 #define GLOBAL_IRQ_KEY_NOT_READY_OFFS 4 #define GLOBAL_IRQ_UNEXPECTED_RESET_OFFS 5 #define GLOBAL_IRQ_QSB_RESP_ERR_OFFS 6 #define GLOBAL_IRQ_WRONG_PIPE_DIR_OFFS 7 #define GLOBAL_IRQ_SC_GO_HIGH_OFFS 8 #define GLOBAL_IRQ_SC_GO_LOW_OFFS 9 #define GLOBAL_IRQ_READ_FAIL_OFFS 16 #define GLOBAL_IRQ_FC_STALL_OFFS 24 /* Bits for TSPP2_PCR_GLOBAL_CONFIG register */ #define PCR_GLOBAL_CONFIG_PCR_ON_DISCONT_OFFS 10 #define PCR_GLOBAL_CONFIG_STC_OFFSET_OFFS 8 #define PCR_GLOBAL_CONFIG_PCR_INTERVAL_OFFS 0 #define PCR_GLOBAL_CONFIG_PCR_ON_DISCONT BIT(10) #define PCR_GLOBAL_CONFIG_STC_OFFSET (BIT(8)|BIT(9)) #define PCR_GLOBAL_CONFIG_PCR_INTERVAL 0xFF /* n = 0..3, each register handles 32 filters */ #define TSPP2_SC_GO_HIGH_STATUS(n) (0x6350 + ((n) << 2)) #define TSPP2_SC_GO_HIGH_CLEAR(n) (0x6360 + ((n) << 2)) #define TSPP2_SC_GO_HIGH_ENABLE(n) (0x6370 + ((n) << 2)) #define TSPP2_SC_GO_LOW_STATUS(n) (0x6390 + ((n) << 2)) #define TSPP2_SC_GO_LOW_CLEAR(n) (0x63A0 + ((n) << 2)) #define TSPP2_SC_GO_LOW_ENABLE(n) (0x63B0 + ((n) << 2)) /* n = 0..3, each register handles 32 contexts */ #define TSPP2_TSP_CONTEXT_RESET(n) (0x6500 + ((n) << 2)) #define TSPP2_PES_CONTEXT_RESET(n) (0x6510 + ((n) << 2)) #define TSPP2_INDEXING_CONTEXT_RESET(n) (0x6520 + ((n) << 2)) /* debugfs entries */ #define TSPP2_S_RW (S_IRUGO | S_IWUSR) struct debugfs_entry { const char *name; mode_t mode; int offset; }; static const struct debugfs_entry tsif_regs[] = { {"sts_ctl", TSPP2_S_RW, TSPP2_TSIF_STS_CTL}, {"time_limit", TSPP2_S_RW, TSPP2_TSIF_TIME_LIMIT}, {"clk_ref", TSPP2_S_RW, TSPP2_TSIF_CLK_REF}, {"lpbk_flags", TSPP2_S_RW, TSPP2_TSIF_LPBK_FLAGS}, {"lpbk_data", TSPP2_S_RW, TSPP2_TSIF_LPBK_DATA}, {"data_port", S_IRUGO, TSPP2_TSIF_DATA_PORT}, }; static const struct debugfs_entry tspp2_regs[] = { /* Memory input source configuration registers */ {"mem_input_src_config_0", TSPP2_S_RW, TSPP2_MEM_INPUT_SRC_CONFIG(0)}, {"mem_input_src_config_1", TSPP2_S_RW, TSPP2_MEM_INPUT_SRC_CONFIG(1)}, {"mem_input_src_config_2", TSPP2_S_RW, TSPP2_MEM_INPUT_SRC_CONFIG(2)}, {"mem_input_src_config_3", TSPP2_S_RW, TSPP2_MEM_INPUT_SRC_CONFIG(3)}, {"mem_input_src_config_4", TSPP2_S_RW, TSPP2_MEM_INPUT_SRC_CONFIG(4)}, {"mem_input_src_config_5", TSPP2_S_RW, TSPP2_MEM_INPUT_SRC_CONFIG(5)}, {"mem_input_src_config_6", TSPP2_S_RW, TSPP2_MEM_INPUT_SRC_CONFIG(6)}, {"mem_input_src_config_7", TSPP2_S_RW, TSPP2_MEM_INPUT_SRC_CONFIG(7)}, /* TSIF input source configuration registers */ {"tsif_input_src_config_0", TSPP2_S_RW, TSPP2_TSIF_INPUT_SRC_CONFIG(0)}, {"tsif_input_src_config_1", TSPP2_S_RW, TSPP2_TSIF_INPUT_SRC_CONFIG(1)}, /* Source destination pipes association registers */ {"src_dest_pipes_0", TSPP2_S_RW, TSPP2_SRC_DEST_PIPES(0)}, {"src_dest_pipes_1", TSPP2_S_RW, TSPP2_SRC_DEST_PIPES(1)}, {"src_dest_pipes_2", TSPP2_S_RW, TSPP2_SRC_DEST_PIPES(2)}, {"src_dest_pipes_3", TSPP2_S_RW, TSPP2_SRC_DEST_PIPES(3)}, {"src_dest_pipes_4", TSPP2_S_RW, TSPP2_SRC_DEST_PIPES(4)}, {"src_dest_pipes_5", TSPP2_S_RW, TSPP2_SRC_DEST_PIPES(5)}, {"src_dest_pipes_6", TSPP2_S_RW, TSPP2_SRC_DEST_PIPES(6)}, {"src_dest_pipes_7", TSPP2_S_RW, TSPP2_SRC_DEST_PIPES(7)}, {"src_dest_pipes_8", TSPP2_S_RW, TSPP2_SRC_DEST_PIPES(8)}, {"src_dest_pipes_9", TSPP2_S_RW, TSPP2_SRC_DEST_PIPES(9)}, /* Source configuration registers */ {"src_config_0", TSPP2_S_RW, TSPP2_SRC_CONFIG(0)}, {"src_config_1", TSPP2_S_RW, TSPP2_SRC_CONFIG(1)}, {"src_config_2", TSPP2_S_RW, TSPP2_SRC_CONFIG(2)}, {"src_config_3", TSPP2_S_RW, TSPP2_SRC_CONFIG(3)}, {"src_config_4", TSPP2_S_RW, TSPP2_SRC_CONFIG(4)}, {"src_config_5", TSPP2_S_RW, TSPP2_SRC_CONFIG(5)}, {"src_config_6", TSPP2_S_RW, TSPP2_SRC_CONFIG(6)}, {"src_config_7", TSPP2_S_RW, TSPP2_SRC_CONFIG(7)}, {"src_config_8", TSPP2_S_RW, TSPP2_SRC_CONFIG(8)}, {"src_config_9", TSPP2_S_RW, TSPP2_SRC_CONFIG(9)}, /* Source total TS packets counter registers */ {"src_total_tsp_0", S_IRUGO, TSPP2_SRC_TOTAL_TSP(0)}, {"src_total_tsp_1", S_IRUGO, TSPP2_SRC_TOTAL_TSP(1)}, {"src_total_tsp_2", S_IRUGO, TSPP2_SRC_TOTAL_TSP(2)}, {"src_total_tsp_3", S_IRUGO, TSPP2_SRC_TOTAL_TSP(3)}, {"src_total_tsp_4", S_IRUGO, TSPP2_SRC_TOTAL_TSP(4)}, {"src_total_tsp_5", S_IRUGO, TSPP2_SRC_TOTAL_TSP(5)}, {"src_total_tsp_6", S_IRUGO, TSPP2_SRC_TOTAL_TSP(6)}, {"src_total_tsp_7", S_IRUGO, TSPP2_SRC_TOTAL_TSP(7)}, {"src_total_tsp_8", S_IRUGO, TSPP2_SRC_TOTAL_TSP(8)}, {"src_total_tsp_9", S_IRUGO, TSPP2_SRC_TOTAL_TSP(9)}, /* Source total filtered out TS packets counter registers */ {"src_filtered_out_tsp_0", S_IRUGO, TSPP2_SRC_FILTERED_OUT_TSP(0)}, {"src_filtered_out_tsp_1", S_IRUGO, TSPP2_SRC_FILTERED_OUT_TSP(1)}, {"src_filtered_out_tsp_2", S_IRUGO, TSPP2_SRC_FILTERED_OUT_TSP(2)}, {"src_filtered_out_tsp_3", S_IRUGO, TSPP2_SRC_FILTERED_OUT_TSP(3)}, {"src_filtered_out_tsp_4", S_IRUGO, TSPP2_SRC_FILTERED_OUT_TSP(4)}, {"src_filtered_out_tsp_5", S_IRUGO, TSPP2_SRC_FILTERED_OUT_TSP(5)}, {"src_filtered_out_tsp_6", S_IRUGO, TSPP2_SRC_FILTERED_OUT_TSP(6)}, {"src_filtered_out_tsp_7", S_IRUGO, TSPP2_SRC_FILTERED_OUT_TSP(7)}, {"src_filtered_out_tsp_8", S_IRUGO, TSPP2_SRC_FILTERED_OUT_TSP(8)}, {"src_filtered_out_tsp_9", S_IRUGO, TSPP2_SRC_FILTERED_OUT_TSP(9)}, /* Global registers */ {"pipe_security", TSPP2_S_RW, TSPP2_PIPE_SECURITY}, {"pcr_global_config", TSPP2_S_RW, TSPP2_PCR_GLOBAL_CONFIG}, {"clk_to_pcr_time_unit", TSPP2_S_RW, TSPP2_CLK_TO_PCR_TIME_UNIT}, {"desc_wait_timeout", TSPP2_S_RW, TSPP2_DESC_WAIT_TIMEOUT}, {"global_irq_status", S_IRUGO, TSPP2_GLOBAL_IRQ_STATUS}, {"global_irq_clear", S_IWUSR, TSPP2_GLOBAL_IRQ_CLEAR}, {"global_irq_en", TSPP2_S_RW, TSPP2_GLOBAL_IRQ_ENABLE}, {"key_not_ready_irq_status", S_IRUGO, TSPP2_KEY_NOT_READY_IRQ_STATUS}, {"key_not_ready_irq_clear", S_IWUSR, TSPP2_KEY_NOT_READY_IRQ_CLEAR}, {"key_not_ready_irq_en", TSPP2_S_RW, TSPP2_KEY_NOT_READY_IRQ_ENABLE}, {"unexpected_rst_irq_status", S_IRUGO, TSPP2_UNEXPECTED_RST_IRQ_STATUS}, {"unexpected_rst_irq_clear", S_IWUSR, TSPP2_UNEXPECTED_RST_IRQ_CLEAR}, {"unexpected_rst_irq_en", TSPP2_S_RW, TSPP2_UNEXPECTED_RST_IRQ_ENABLE}, {"wrong_pipe_dir_irq_status", S_IRUGO, TSPP2_WRONG_PIPE_DIR_IRQ_STATUS}, {"wrong_pipe_dir_irq_clear", S_IWUSR, TSPP2_WRONG_PIPE_DIR_IRQ_CLEAR}, {"wrong_pipe_dir_irq_en", TSPP2_S_RW, TSPP2_WRONG_PIPE_DIR_IRQ_ENABLE}, {"qsb_response_error_irq_status", S_IRUGO, TSPP2_QSB_RESPONSE_ERROR_IRQ_STATUS}, {"qsb_response_error_irq_clear", S_IWUSR, TSPP2_QSB_RESPONSE_ERROR_IRQ_CLEAR}, {"qsb_response_error_irq_en", TSPP2_S_RW, TSPP2_QSB_RESPONSE_ERROR_IRQ_ENABLE}, {"src_total_tsp_reset", S_IWUSR, TSPP2_SRC_TOTAL_TSP_RESET}, {"src_filtered_out_tsp_reset", S_IWUSR, TSPP2_SRC_FILTERED_OUT_TSP_RESET}, {"data_not_sent_on_pipe_reset", S_IWUSR, TSPP2_DATA_NOT_SENT_ON_PIPE_RESET}, {"version", S_IRUGO, TSPP2_VERSION}, /* Scrambling bits monitoring interrupt registers */ {"sc_go_high_status_0", S_IRUGO, TSPP2_SC_GO_HIGH_STATUS(0)}, {"sc_go_high_status_1", S_IRUGO, TSPP2_SC_GO_HIGH_STATUS(1)}, {"sc_go_high_status_2", S_IRUGO, TSPP2_SC_GO_HIGH_STATUS(2)}, {"sc_go_high_status_3", S_IRUGO, TSPP2_SC_GO_HIGH_STATUS(3)}, {"sc_go_high_clear_0", S_IWUSR, TSPP2_SC_GO_HIGH_CLEAR(0)}, {"sc_go_high_clear_1", S_IWUSR, TSPP2_SC_GO_HIGH_CLEAR(1)}, {"sc_go_high_clear_2", S_IWUSR, TSPP2_SC_GO_HIGH_CLEAR(2)}, {"sc_go_high_clear_3", S_IWUSR, TSPP2_SC_GO_HIGH_CLEAR(3)}, {"sc_go_high_en_0", TSPP2_S_RW, TSPP2_SC_GO_HIGH_ENABLE(0)}, {"sc_go_high_en_1", TSPP2_S_RW, TSPP2_SC_GO_HIGH_ENABLE(1)}, {"sc_go_high_en_2", TSPP2_S_RW, TSPP2_SC_GO_HIGH_ENABLE(2)}, {"sc_go_high_en_3", TSPP2_S_RW, TSPP2_SC_GO_HIGH_ENABLE(3)}, {"sc_go_low_status_0", S_IRUGO, TSPP2_SC_GO_LOW_STATUS(0)}, {"sc_go_low_status_1", S_IRUGO, TSPP2_SC_GO_LOW_STATUS(1)}, {"sc_go_low_status_2", S_IRUGO, TSPP2_SC_GO_LOW_STATUS(2)}, {"sc_go_low_status_3", S_IRUGO, TSPP2_SC_GO_LOW_STATUS(3)}, {"sc_go_low_clear_0", S_IWUSR, TSPP2_SC_GO_LOW_CLEAR(0)}, {"sc_go_low_clear_1", S_IWUSR, TSPP2_SC_GO_LOW_CLEAR(1)}, {"sc_go_low_clear_2", S_IWUSR, TSPP2_SC_GO_LOW_CLEAR(2)}, {"sc_go_low_clear_3", S_IWUSR, TSPP2_SC_GO_LOW_CLEAR(3)}, {"sc_go_low_en_0", TSPP2_S_RW, TSPP2_SC_GO_LOW_ENABLE(0)}, {"sc_go_low_en_1", TSPP2_S_RW, TSPP2_SC_GO_LOW_ENABLE(1)}, {"sc_go_low_en_2", TSPP2_S_RW, TSPP2_SC_GO_LOW_ENABLE(2)}, {"sc_go_low_en_3", TSPP2_S_RW, TSPP2_SC_GO_LOW_ENABLE(3)}, }; /* Data structures */ /** * struct tspp2_tsif_device - TSIF device * * @base: TSIF device memory base address. * @hw_index: TSIF device HW index (0 .. (TSPP2_NUM_TSIF_INPUTS - 1)). * @dev: Back pointer to the TSPP2 device. * @time_limit: TSIF device time limit * (maximum time allowed between each TS packet). * @ref_count: TSIF device reference count. * @tsif_irq: TSIF device IRQ number. * @mode: TSIF mode of operation. * @clock_inverse: Invert input clock signal. * @data_inverse: Invert input data signal. * @sync_inverse: Invert input sync signal. * @enable_inverse: Invert input enable signal. * @debugfs_entrys: TSIF device debugfs entry. * @stat_pkt_write_err: TSIF device packet write error statistics. * @stat__pkt_read_err: TSIF device packet read error statistics. * @stat_overflow: TSIF device overflow statistics. * @stat_lost_sync: TSIF device lost sync statistics. * @stat_timeout: TSIF device timeout statistics. */ struct tspp2_tsif_device { void __iomem *base; u32 hw_index; struct tspp2_device *dev; u32 time_limit; u32 ref_count; u32 tsif_irq; enum tspp2_tsif_mode mode; int clock_inverse; int data_inverse; int sync_inverse; int enable_inverse; struct dentry *debugfs_entry; u32 stat_pkt_write_err; u32 stat_pkt_read_err; u32 stat_overflow; u32 stat_lost_sync; u32 stat_timeout; }; /** * struct tspp2_indexing_table - Indexing table * * @prefix_value: 4-byte common prefix value. * @prefix_mask: 4-byte prefix mask. * @entry_value: An array of 4-byte pattern values. * @entry_mask: An array of corresponding 4-byte pattern masks. * @num_valid_entries: Number of valid entries in the arrays. */ struct tspp2_indexing_table { u32 prefix_value; u32 prefix_mask; u32 entry_value[TSPP2_NUM_INDEXING_PATTERNS]; u32 entry_mask[TSPP2_NUM_INDEXING_PATTERNS]; u16 num_valid_entries; }; /** * struct tspp2_event_work - Event work information * * @device: TSPP2 device back-pointer. * @callback: Callback to invoke. * @cookie: Cookie to pass to the callback. * @event_bitmask: A bit mask of events to pass to the callback. * @work: The work structure to queue. * @link: A list element. */ struct tspp2_event_work { struct tspp2_device *device; void (*callback)(void *cookie, u32 event_bitmask); void *cookie; u32 event_bitmask; struct work_struct work; struct list_head link; }; /** * struct tspp2_filter - Filter object * * @opened: A flag to indicate whether the filter is open. * @device: Back-pointer to the TSPP2 device the filter * belongs to. * @batch: The filter batch this filter belongs to. * @src: Back-pointer to the source the filter is * associated with. * @hw_index: The filter's HW index. * @pid_value: The filter's 13-bit PID value. * @mask: The corresponding 13-bit bitmask. * @context: The filter's context ID. * @indexing_table_id: The ID of the indexing table this filter uses * in case an indexing operation is set. * @operations: An array of user-defined operations. * @num_user_operations: The number of user-defined operations. * @indexing_op_set: A flag to indicate an indexing operation * has been set. * @raw_op_with_indexing: A flag to indicate a Raw Transmit operation * with support_indexing parameter has been set. * @pes_analysis_op_set: A flag to indicate a PES Analysis operation * has been set. * @raw_op_set: A flag to indicate a Raw Transmit operation * has been set. * @pes_tx_op_set: A flag to indicate a PES Transmit operation * has been set. * @event_callback: A user callback to invoke when a filter event * occurs. * @event_cookie: A user cookie to provide to the callback. * @event_bitmask: A bit mask of filter events * TSPP2_FILTER_EVENT_XXX. * @enabled: A flag to indicate whether the filter * is enabled. * @link: A list element. When the filter is associated * with a source, it is added to the source's * list of filters. */ struct tspp2_filter { int opened; struct tspp2_device *device; struct tspp2_filter_batch *batch; struct tspp2_src *src; u16 hw_index; u16 pid_value; u16 mask; u16 context; u8 indexing_table_id; struct tspp2_operation operations[TSPP2_MAX_OPS_PER_FILTER]; u8 num_user_operations; int indexing_op_set; int raw_op_with_indexing; int pes_analysis_op_set; int raw_op_set; int pes_tx_op_set; void (*event_callback)(void *cookie, u32 event_bitmask); void *event_cookie; u32 event_bitmask; int enabled; struct list_head link; }; /** * struct tspp2_pipe - Pipe object * * @opened: A flag to indicate whether the pipe is open. * @device: Back-pointer to the TSPP2 device the pipe belongs to. * @cfg: Pipe configuration parameters. * @sps_pipe: The BAM SPS pipe. * @sps_connect_cfg: SPS pipe connection configuration. * @sps_event: SPS pipe event registration parameters. * @desc_ion_handle: ION handle for the SPS pipe descriptors. * @iova: TSPP2 IOMMU-mapped virtual address of the * data buffer provided by the user. * @hw_index: The pipe's HW index (for register access). * @threshold: Pipe threshold. * @ref_cnt: Pipe reference count. Incremented when pipe * is attached to a source, decremented when it * is detached from a source. */ struct tspp2_pipe { int opened; struct tspp2_device *device; struct tspp2_pipe_config_params cfg; struct sps_pipe *sps_pipe; struct sps_connect sps_connect_cfg; struct sps_register_event sps_event; struct ion_handle *desc_ion_handle; ion_phys_addr_t iova; u32 hw_index; u16 threshold; u32 ref_cnt; }; /** * struct tspp2_output_pipe - Output pipe element to add to a source's list * * @pipe: A pointer to an output pipe object. * @link: A list element. When an output pipe is attached to a source, * it is added to the source's output pipe list. Note the same pipe * can be attached to multiple sources, so we allocate an output * pipe element to add to the list - we don't add the actual pipe. */ struct tspp2_output_pipe { struct tspp2_pipe *pipe; struct list_head link; }; /** * struct tspp2_filter_batch - Filter batch object * * @batch_id: Filter batch ID. * @hw_filters: An array of HW filters that belong to this batch. When set, this * indicates the filter is used. The actual HW index of a filter is * calculated according to the index in this array along with the * batch ID. * @src: Back-pointer to the source the batch is associated with. This is * also used to indicate this batch is "taken". * @link: A list element. When the batch is associated with a source, it * is added to the source's list of filter batches. */ struct tspp2_filter_batch { u8 batch_id; int hw_filters[TSPP2_FILTERS_PER_BATCH]; struct tspp2_src *src; struct list_head link; }; /** * struct tspp2_src - Source object * * @opened: A flag to indicate whether the source is open. * @device: Back-pointer to the TSPP2 device the source * belongs to. * @hw_index: The source's HW index. This is used when writing * to HW registers relevant for this source. * There are registers specific to TSIF or memory * sources, and there are registers common to all * sources. * @input: Source input type (TSIF / memory). * @pkt_format: Input packet size and format for this source. * @scrambling_bits_monitoring: Scrambling bits monitoring mode. * @batches_list: A list of associated filter batches. * @filters_list: A list of associated filters. * @input_pipe: A pointer to the source's input pipe, if exists. * @output_pipe_list: A list of output pipes attached to the source. * For each pipe we also save whether it is * stalling for this source. * @num_associated_batches: Number of associated filter batches. * @num_associated_pipes: Number of associated pipes. * @num_associated_filters: Number of associated filters. * @reserved_filter_hw_index: A HW filter index reserved for updating an * active filter's operations. * @event_callback: A user callback to invoke when a source event * occurs. * @event_cookie: A user cookie to provide to the callback. * @event_bitmask: A bit mask of source events * TSPP2_SRC_EVENT_XXX. * @enabled: A flag to indicate whether the source * is enabled. */ struct tspp2_src { int opened; struct tspp2_device *device; u8 hw_index; enum tspp2_src_input input; enum tspp2_packet_format pkt_format; enum tspp2_src_scrambling_monitoring scrambling_bits_monitoring; struct list_head batches_list; struct list_head filters_list; struct tspp2_pipe *input_pipe; struct list_head output_pipe_list; u8 num_associated_batches; u8 num_associated_pipes; u32 num_associated_filters; u16 reserved_filter_hw_index; void (*event_callback)(void *cookie, u32 event_bitmask); void *event_cookie; u32 event_bitmask; int enabled; }; /** * struct tspp2_global_irq_stats - Global interrupt statistics counters * * @tsp_invalid_af_control: Invalid adaptation field control bit. * @tsp_invalid_length: Invalid adaptation field length. * @pes_no_sync: PES sync sequence not found. * @encrypt_level_err: Cipher operation configuration error. */ struct tspp2_global_irq_stats { u32 tsp_invalid_af_control; u32 tsp_invalid_length; u32 pes_no_sync; u32 encrypt_level_err; }; /** * struct tspp2_src_irq_stats - Memory source interrupt statistics counters * * @read_failure: Failure to read from memory input. * @flow_control_stall: Input is stalled due to flow control. */ struct tspp2_src_irq_stats { u32 read_failure; u32 flow_control_stall; }; /** * struct tspp2_keytable_irq_stats - Key table interrupt statistics counters * * @key_not_ready: Ciphering keys are not ready in the key table. */ struct tspp2_keytable_irq_stats { u32 key_not_ready; }; /** * struct tspp2_pipe_irq_stats - Pipe interrupt statistics counters * * @unexpected_reset: SW reset the pipe before all operations on this * pipe ended. * @qsb_response_error: TX operation ends with QSB error. * @wrong_pipe_direction: Trying to use a pipe in the wrong direction. */ struct tspp2_pipe_irq_stats { u32 unexpected_reset; u32 qsb_response_error; u32 wrong_pipe_direction; }; /** * struct tspp2_filter_context_irq_stats - Filter interrupt statistics counters * * @sc_go_high: Scrambling bits change from clear to encrypted. * @sc_go_low: Scrambling bits change from encrypted to clear. */ struct tspp2_filter_context_irq_stats { u32 sc_go_high; u32 sc_go_low; }; /** * struct tspp2_irq_stats - Interrupt statistics counters * * @global: Global interrupt statistics counters * @src: Memory source interrupt statistics counters * @kt: Key table interrupt statistics counters * @pipe: Pipe interrupt statistics counters * @ctx: Filter context interrupt statistics counters */ struct tspp2_irq_stats { struct tspp2_global_irq_stats global; struct tspp2_src_irq_stats src[TSPP2_NUM_MEM_INPUTS]; struct tspp2_keytable_irq_stats kt[TSPP2_NUM_KEYTABLES]; struct tspp2_pipe_irq_stats pipe[TSPP2_NUM_PIPES]; struct tspp2_filter_context_irq_stats ctx[TSPP2_NUM_CONTEXTS]; }; /** * struct tspp2_iommu_info - TSPP2 IOMMU information * * @hlos_group: TSPP2 IOMMU HLOS (Non-Secure) group. * @cpz_group: TSPP2 IOMMU HLOS (Secure) group. * @hlos_domain: TSPP2 IOMMU HLOS (Non-Secure) domain. * @cpz_domain: TSPP2 IOMMU CPZ (Secure) domain. * @hlos_domain_num: TSPP2 IOMMU HLOS (Non-Secure) domain number. * @cpz_domain_num: TSPP2 IOMMU CPZ (Secure) domain number. * @hlos_partition: TSPP2 IOMMU HLOS partition number. * @cpz_partition: TSPP2 IOMMU CPZ partition number. */ struct tspp2_iommu_info { struct iommu_group *hlos_group; struct iommu_group *cpz_group; struct iommu_domain *hlos_domain; struct iommu_domain *cpz_domain; int hlos_domain_num; int cpz_domain_num; int hlos_partition; int cpz_partition; }; /** * struct tspp2_device - TSPP2 device * * @dev_id: TSPP2 device ID. * @opened: A flag to indicate whether the device is open. * @pdev: Platform device. * @dev: Device structure, used for driver prints. * @base: TSPP2 Device memory base address. * @tspp2_irq: TSPP2 Device IRQ number. * @bam_handle: BAM handle. * @bam_irq: BAM IRQ number. * @bam_props: BAM properties. * @iommu_info: IOMMU information. * @wakeup_src: A wakeup source to keep CPU awake when needed. * @spinlock: A spinlock to protect accesses to * data structures that happen from APIs and ISRs. * @mutex: A mutex for mutual exclusion between API calls. * @tsif_devices: An array of TSIF devices. * @gdsc: GDSC power regulator. * @bus_client: Client for bus bandwidth voting. * @tspp2_ahb_clk: TSPP2 AHB clock. * @tspp2_core_clk: TSPP2 core clock. * @tspp2_vbif_clk: TSPP2 VBIF clock. * @vbif_ahb_clk: VBIF AHB clock. * @vbif_axi_clk: VBIF AXI clock. * @tspp2_klm_ahb_clk: TSPP2 KLM AHB clock. * @tsif_ref_clk: TSIF reference clock. * @batches: An array of filter batch objects. * @contexts: An array of context indexes. The index in this * array represents the context's HW index, while * the value represents whether it is used by a * filter or free. * @indexing_tables: An array of indexing tables. * @tsif_sources: An array of source objects for TSIF input. * @mem_sources: An array of source objects for memory input. * @filters: An array of filter objects. * @pipes: An array of pipe objects. * @num_secured_opened_pipes: Number of secured opened pipes. * @num_non_secured_opened_pipes: Number of non-secured opened pipes. * @num_enabled_sources: Number of enabled sources. * @work_queue: A work queue for invoking user callbacks. * @event_callback: A user callback to invoke when a global event * occurs. * @event_cookie: A user cookie to provide to the callback. * @event_bitmask: A bit mask of global events * TSPP2_GLOBAL_EVENT_XXX. * @debugfs_entry: TSPP2 device debugfs entry. * @irq_stats: TSPP2 IRQ statistics. * @free_work_list: A list of available work elements. * @work_pool: A pool of work elements. */ struct tspp2_device { u32 dev_id; int opened; struct platform_device *pdev; struct device *dev; void __iomem *base; u32 tspp2_irq; unsigned long bam_handle; u32 bam_irq; struct sps_bam_props bam_props; struct tspp2_iommu_info iommu_info; struct wakeup_source wakeup_src; spinlock_t spinlock; struct mutex mutex; struct tspp2_tsif_device tsif_devices[TSPP2_NUM_TSIF_INPUTS]; struct regulator *gdsc; uint32_t bus_client; struct clk *tspp2_ahb_clk; struct clk *tspp2_core_clk; struct clk *tspp2_vbif_clk; struct clk *vbif_ahb_clk; struct clk *vbif_axi_clk; struct clk *tspp2_klm_ahb_clk; struct clk *tsif_ref_clk; struct tspp2_filter_batch batches[TSPP2_NUM_BATCHES]; int contexts[TSPP2_NUM_AVAIL_CONTEXTS]; struct tspp2_indexing_table indexing_tables[TSPP2_NUM_INDEXING_TABLES]; struct tspp2_src tsif_sources[TSPP2_NUM_TSIF_INPUTS]; struct tspp2_src mem_sources[TSPP2_NUM_MEM_INPUTS]; struct tspp2_filter filters[TSPP2_NUM_AVAIL_FILTERS]; struct tspp2_pipe pipes[TSPP2_NUM_PIPES]; u8 num_secured_opened_pipes; u8 num_non_secured_opened_pipes; u8 num_enabled_sources; struct workqueue_struct *work_queue; void (*event_callback)(void *cookie, u32 event_bitmask); void *event_cookie; u32 event_bitmask; struct dentry *debugfs_entry; struct tspp2_irq_stats irq_stats; struct list_head free_work_list; struct tspp2_event_work work_pool[TSPP2_NUM_EVENT_WORK_ELEMENTS]; }; /* Global TSPP2 devices database */ static struct tspp2_device *tspp2_devices[TSPP2_NUM_DEVICES]; /* debugfs support */ static int debugfs_iomem_x32_set(void *data, u64 val) { int ret; struct tspp2_device *device = tspp2_devices[0]; /* Assuming device 0 */ if (!device->opened) return -ENODEV; ret = pm_runtime_get_sync(device->dev); if (ret < 0) return ret; writel_relaxed(val, data); wmb(); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); return 0; } static int debugfs_iomem_x32_get(void *data, u64 *val) { int ret; struct tspp2_device *device = tspp2_devices[0]; /* Assuming device 0 */ if (!device->opened) return -ENODEV; ret = pm_runtime_get_sync(device->dev); if (ret < 0) return ret; *val = readl_relaxed(data); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); return 0; } DEFINE_SIMPLE_ATTRIBUTE(fops_iomem_x32, debugfs_iomem_x32_get, debugfs_iomem_x32_set, "0x%08llX"); static int debugfs_dev_open_set(void *data, u64 val) { int ret = 0; /* Assuming device 0 */ if (val == 1) ret = tspp2_device_open(0); else ret = tspp2_device_close(0); return ret; } static int debugfs_dev_open_get(void *data, u64 *val) { struct tspp2_device *device = tspp2_devices[0]; /* Assuming device 0 */ *val = device->opened; return 0; } DEFINE_SIMPLE_ATTRIBUTE(fops_device_open, debugfs_dev_open_get, debugfs_dev_open_set, "0x%08llX"); /** * tspp2_tsif_debugfs_init() - TSIF device debugfs initialization. * * @tsif_device: TSIF device. */ static void tspp2_tsif_debugfs_init(struct tspp2_tsif_device *tsif_device) { int i; char name[10]; struct dentry *dentry; void __iomem *base = tsif_device->base; snprintf(name, 10, "tsif%i", tsif_device->hw_index); tsif_device->debugfs_entry = debugfs_create_dir(name, NULL); if (!tsif_device->debugfs_entry) return; dentry = tsif_device->debugfs_entry; if (dentry) { for (i = 0; i < ARRAY_SIZE(tsif_regs); i++) { debugfs_create_file( tsif_regs[i].name, tsif_regs[i].mode, dentry, base + tsif_regs[i].offset, &fops_iomem_x32); } } dentry = debugfs_create_dir("statistics", tsif_device->debugfs_entry); if (dentry) { debugfs_create_u32( "stat_pkt_write_err", S_IRUGO | S_IWUSR | S_IWGRP, dentry, &tsif_device->stat_pkt_write_err); debugfs_create_u32( "stat_pkt_read_err", S_IRUGO | S_IWUSR | S_IWGRP, dentry, &tsif_device->stat_pkt_read_err); debugfs_create_u32( "stat_overflow", S_IRUGO | S_IWUSR | S_IWGRP, dentry, &tsif_device->stat_overflow); debugfs_create_u32( "stat_lost_sync", S_IRUGO | S_IWUSR | S_IWGRP, dentry, &tsif_device->stat_lost_sync); debugfs_create_u32( "stat_timeout", S_IRUGO | S_IWUSR | S_IWGRP, dentry, &tsif_device->stat_timeout); } } static char *op_to_string(enum tspp2_operation_type op) { switch (op) { case TSPP2_OP_PES_ANALYSIS: return "TSPP2_OP_PES_ANALYSIS"; case TSPP2_OP_RAW_TRANSMIT: return "TSPP2_OP_RAW_TRANSMIT"; case TSPP2_OP_PES_TRANSMIT: return "TSPP2_OP_PES_TRANSMIT"; case TSPP2_OP_PCR_EXTRACTION: return "TSPP2_OP_PCR_EXTRACTION"; case TSPP2_OP_CIPHER: return "TSPP2_OP_CIPHER"; case TSPP2_OP_INDEXING: return "TSPP2_OP_INDEXING"; case TSPP2_OP_COPY_PACKET: return "TSPP2_OP_COPY_PACKET"; default: return "Invalid Operation"; } } static char *src_input_to_string(enum tspp2_src_input src_input) { switch (src_input) { case TSPP2_INPUT_TSIF0: return "TSPP2_INPUT_TSIF0"; case TSPP2_INPUT_TSIF1: return "TSPP2_INPUT_TSIF1"; case TSPP2_INPUT_MEMORY: return "TSPP2_INPUT_MEMORY"; default: return "Unknown source input type"; } } static char *pkt_format_to_string(enum tspp2_packet_format pkt_format) { switch (pkt_format) { case TSPP2_PACKET_FORMAT_188_RAW: return "TSPP2_PACKET_FORMAT_188_RAW"; case TSPP2_PACKET_FORMAT_192_HEAD: return "TSPP2_PACKET_FORMAT_192_HEAD"; case TSPP2_PACKET_FORMAT_192_TAIL: return "TSPP2_PACKET_FORMAT_192_TAIL"; default: return "Unknown packet format"; } } /** * debugfs service to print device information. */ static int tspp2_device_debugfs_print(struct seq_file *s, void *p) { int count; int exist_flag = 0; struct tspp2_device *device = (struct tspp2_device *)s->private; seq_printf(s, "dev_id: %d\n", device->dev_id); seq_puts(s, "Enabled filters:"); for (count = 0; count < TSPP2_NUM_AVAIL_FILTERS; count++) if (device->filters[count].enabled) { seq_printf(s, "\n\tfilter%3d", count); exist_flag = 1; } if (!exist_flag) seq_puts(s, " none\n"); else seq_puts(s, "\n"); exist_flag = 0; seq_puts(s, "Opened filters:"); for (count = 0; count < TSPP2_NUM_AVAIL_FILTERS; count++) if (device->filters[count].opened) { seq_printf(s, "\n\tfilter%3d", count); exist_flag = 1; } if (!exist_flag) seq_puts(s, " none\n"); else seq_puts(s, "\n"); exist_flag = 0; seq_puts(s, "Opened pipes:\n"); for (count = 0; count < TSPP2_NUM_PIPES; count++) if (device->pipes[count].opened) { seq_printf(s, "\tpipe%2d\n", count); exist_flag = 1; } if (!exist_flag) seq_puts(s, " none\n"); else seq_puts(s, "\n"); return 0; } /** * debugfs service to print source information. */ static int tspp2_src_debugfs_print(struct seq_file *s, void *p) { struct tspp2_filter_batch *batch; struct tspp2_filter *filter; struct tspp2_output_pipe *output_pipe; struct tspp2_src *src = (struct tspp2_src *)s->private; if (!src) { seq_puts(s, "error\n"); return 1; } seq_printf(s, "Status: %s\n", src->enabled ? "enabled" : "disabled"); seq_printf(s, "hw_index: %d\n", src->hw_index); seq_printf(s, "event_bitmask: 0x%08X\n", src->event_bitmask); if (src->input_pipe) seq_printf(s, "input_pipe hw_index: %d\n", src->input_pipe->hw_index); seq_printf(s, "tspp2_src_input: %s\n", src_input_to_string(src->input)); seq_printf(s, "pkt_format: %s\n", pkt_format_to_string(src->pkt_format)); seq_printf(s, "num_associated_batches: %d\n", src->num_associated_batches); if (src->num_associated_batches) { seq_puts(s, "batch_ids: "); list_for_each_entry(batch, &src->batches_list, link) seq_printf(s, "%d ", batch->batch_id); seq_puts(s, "\n"); } seq_printf(s, "num_associated_pipes: %d\n", src->num_associated_pipes); if (src->num_associated_pipes) { seq_puts(s, "pipes_hw_idxs: "); list_for_each_entry(output_pipe, &src->output_pipe_list, link) { seq_printf(s, "%d ", output_pipe->pipe->hw_index); } seq_puts(s, "\n"); } seq_printf(s, "reserved_filter_hw_index: %d\n", src->reserved_filter_hw_index); seq_printf(s, "num_associated_filters: %d\n", src->num_associated_filters); if (src->num_associated_filters) { int i; seq_puts(s, "Open filters:\n"); list_for_each_entry(filter, &src->filters_list, link) { if (!filter->opened) continue; seq_printf(s, "\thw_index: %d\n", filter->hw_index); seq_printf(s, "\tStatus: %s\n", filter->enabled ? "enabled" : "disabled"); seq_printf(s, "\tpid_value: 0x%08X\n", filter->pid_value); seq_printf(s, "\tmask: 0x%08X\n", filter->mask); seq_printf(s, "\tnum_user_operations: %d\n", filter->num_user_operations); if (filter->num_user_operations) { seq_puts( s, "\tTypes of operations:\n"); for (i = 0; i < filter->num_user_operations; i++) { seq_printf(s, "\t\t%s\n", op_to_string( filter->operations[i].type)); } } } } else { seq_puts(s, "no filters\n"); } return 0; } /** * debugfs service to print filter information. */ static int filter_debugfs_print(struct seq_file *s, void *p) { int i; struct tspp2_filter *filter = (struct tspp2_filter *)s->private; seq_printf(s, "Status: %s\n", filter->opened ? "opened" : "closed"); if (filter->batch) seq_printf(s, "Located in batch %d\n", filter->batch->batch_id); if (filter->src) seq_printf(s, "Associated with src %d\n", filter->src->hw_index); seq_printf(s, "hw_index: %d\n", filter->hw_index); seq_printf(s, "pid_value: 0x%08X\n", filter->pid_value); seq_printf(s, "mask: 0x%08X\n", filter->mask); seq_printf(s, "context: %d\n", filter->context); seq_printf(s, "indexing_table_id: %d\n", filter->indexing_table_id); seq_printf(s, "num_user_operations: %d\n", filter->num_user_operations); seq_puts(s, "Types of operations:\n"); for (i = 0; i < filter->num_user_operations; i++) seq_printf(s, "\t%s\n", op_to_string( filter->operations[i].type)); seq_printf(s, "indexing_op_set: %d\n", filter->indexing_op_set); seq_printf(s, "raw_op_with_indexing: %d\n", filter->raw_op_with_indexing); seq_printf(s, "pes_analysis_op_set: %d\n", filter->pes_analysis_op_set); seq_printf(s, "raw_op_set: %d\n", filter->raw_op_set); seq_printf(s, "pes_tx_op_set: %d\n", filter->pes_tx_op_set); seq_printf(s, "Status: %s\n", filter->enabled ? "enabled" : "disabled"); if (filter->enabled) { seq_printf(s, "Filter context-based counters, context %d\n", filter->context); seq_printf(s, "filter_tsp_sync_err = 0x%08X\n", readl_relaxed(filter->device->base + TSPP2_FILTER_TSP_SYNC_ERROR(filter->context))); seq_printf(s, "filter_erred_tsp = 0x%08X\n", readl_relaxed(filter->device->base + TSPP2_FILTER_ERRED_TSP(filter->context))); seq_printf(s, "filter_discontinuities = 0x%08X\n", readl_relaxed(filter->device->base + TSPP2_FILTER_DISCONTINUITIES(filter->context))); seq_printf(s, "filter_sc_bits_discard = 0x%08X\n", readl_relaxed(filter->device->base + TSPP2_FILTER_SCRAMBLING_BITS_DISCARD(filter->context))); seq_printf(s, "filter_tsp_total_num = 0x%08X\n", readl_relaxed(filter->device->base + TSPP2_FILTER_TSP_TOTAL_NUM(filter->context))); seq_printf(s, "filter_discont_indicator = 0x%08X\n", readl_relaxed(filter->device->base + TSPP2_FILTER_DISCONT_INDICATOR(filter->context))); seq_printf(s, "filter_tsp_no_payload = 0x%08X\n", readl_relaxed(filter->device->base + TSPP2_FILTER_TSP_NO_PAYLOAD(filter->context))); seq_printf(s, "filter_tsp_duplicate = 0x%08X\n", readl_relaxed(filter->device->base + TSPP2_FILTER_TSP_DUPLICATE(filter->context))); seq_printf(s, "filter_key_fetch_fail = 0x%08X\n", readl_relaxed(filter->device->base + TSPP2_FILTER_KEY_FETCH_FAILURE(filter->context))); seq_printf(s, "filter_dropped_pcr = 0x%08X\n", readl_relaxed(filter->device->base + TSPP2_FILTER_DROPPED_PCR(filter->context))); seq_printf(s, "filter_pes_errors = 0x%08X\n", readl_relaxed(filter->device->base + TSPP2_FILTER_PES_ERRORS(filter->context))); } return 0; } /** * debugfs service to print pipe information. */ static int pipe_debugfs_print(struct seq_file *s, void *p) { struct tspp2_pipe *pipe = (struct tspp2_pipe *)s->private; seq_printf(s, "hw_index: %d\n", pipe->hw_index); seq_printf(s, "iova: 0x%08X\n", pipe->iova); seq_printf(s, "threshold: %d\n", pipe->threshold); seq_printf(s, "Status: %s\n", pipe->opened ? "opened" : "closed"); seq_printf(s, "ref_cnt: %d\n", pipe->ref_cnt); return 0; } static int tspp2_dev_dbgfs_open(struct inode *inode, struct file *file) { return single_open(file, tspp2_device_debugfs_print, inode->i_private); } static int tspp2_filter_dbgfs_open(struct inode *inode, struct file *file) { return single_open(file, filter_debugfs_print, inode->i_private); } static int tspp2_pipe_dbgfs_open(struct inode *inode, struct file *file) { return single_open(file, pipe_debugfs_print, inode->i_private); } static int tspp2_src_dbgfs_open(struct inode *inode, struct file *file) { return single_open(file, tspp2_src_debugfs_print, inode->i_private); } static const struct file_operations dbgfs_tspp2_device_fops = { .open = tspp2_dev_dbgfs_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, .owner = THIS_MODULE, }; static const struct file_operations dbgfs_filter_fops = { .open = tspp2_filter_dbgfs_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, .owner = THIS_MODULE, }; static const struct file_operations dbgfs_pipe_fops = { .open = tspp2_pipe_dbgfs_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, .owner = THIS_MODULE, }; static const struct file_operations dbgfs_src_fops = { .open = tspp2_src_dbgfs_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, .owner = THIS_MODULE, }; /** * tspp2_tsif_debugfs_exit() - TSIF device debugfs teardown. * * @tsif_device: TSIF device. */ static void tspp2_tsif_debugfs_exit(struct tspp2_tsif_device *tsif_device) { debugfs_remove_recursive(tsif_device->debugfs_entry); tsif_device->debugfs_entry = NULL; } /** * tspp2_debugfs_init() - TSPP2 device debugfs initialization. * * @device: TSPP2 device. */ static void tspp2_debugfs_init(struct tspp2_device *device) { int i, j; char name[80]; struct dentry *dentry; struct dentry *dir; void __iomem *base = device->base; snprintf(name, 80, "tspp2_%i", device->dev_id); device->debugfs_entry = debugfs_create_dir(name, NULL); if (!device->debugfs_entry) return; /* Support device open/close */ debugfs_create_file("open", TSPP2_S_RW, device->debugfs_entry, NULL, &fops_device_open); dentry = debugfs_create_dir("regs", device->debugfs_entry); if (dentry) { for (i = 0; i < ARRAY_SIZE(tspp2_regs); i++) { debugfs_create_file( tspp2_regs[i].name, tspp2_regs[i].mode, dentry, base + tspp2_regs[i].offset, &fops_iomem_x32); } } dentry = debugfs_create_dir("statistics", device->debugfs_entry); if (dentry) { debugfs_create_u32( "stat_tsp_invalid_af_control", S_IRUGO | S_IWUSR | S_IWGRP, dentry, &device->irq_stats.global.tsp_invalid_af_control); debugfs_create_u32( "stat_tsp_invalid_length", S_IRUGO | S_IWUSR | S_IWGRP, dentry, &device->irq_stats.global.tsp_invalid_length); debugfs_create_u32( "stat_pes_no_sync", S_IRUGO | S_IWUSR | S_IWGRP, dentry, &device->irq_stats.global.pes_no_sync); debugfs_create_u32( "stat_encrypt_level_err", S_IRUGO | S_IWUSR | S_IWGRP, dentry, &device->irq_stats.global.encrypt_level_err); } dir = debugfs_create_dir("counters", device->debugfs_entry); for (i = 0; i < TSPP2_NUM_CONTEXTS; i++) { snprintf(name, 80, "context%03i", i); dentry = debugfs_create_dir(name, dir); if (dentry) { debugfs_create_file("filter_tsp_sync_err", TSPP2_S_RW, dentry, base + TSPP2_FILTER_TSP_SYNC_ERROR(i), &fops_iomem_x32); debugfs_create_file("filter_erred_tsp", TSPP2_S_RW, dentry, base + TSPP2_FILTER_ERRED_TSP(i), &fops_iomem_x32); debugfs_create_file("filter_discontinuities", TSPP2_S_RW, dentry, base + TSPP2_FILTER_DISCONTINUITIES(i), &fops_iomem_x32); debugfs_create_file("filter_sc_bits_discard", TSPP2_S_RW, dentry, base + TSPP2_FILTER_SCRAMBLING_BITS_DISCARD(i), &fops_iomem_x32); debugfs_create_file("filter_tsp_total_num", TSPP2_S_RW, dentry, base + TSPP2_FILTER_TSP_TOTAL_NUM(i), &fops_iomem_x32); debugfs_create_file("filter_discont_indicator", TSPP2_S_RW, dentry, base + TSPP2_FILTER_DISCONT_INDICATOR(i), &fops_iomem_x32); debugfs_create_file("filter_tsp_no_payload", TSPP2_S_RW, dentry, base + TSPP2_FILTER_TSP_NO_PAYLOAD(i), &fops_iomem_x32); debugfs_create_file("filter_tsp_duplicate", TSPP2_S_RW, dentry, base + TSPP2_FILTER_TSP_DUPLICATE(i), &fops_iomem_x32); debugfs_create_file("filter_key_fetch_fail", TSPP2_S_RW, dentry, base + TSPP2_FILTER_KEY_FETCH_FAILURE(i), &fops_iomem_x32); debugfs_create_file("filter_dropped_pcr", TSPP2_S_RW, dentry, base + TSPP2_FILTER_DROPPED_PCR(i), &fops_iomem_x32); debugfs_create_file("filter_pes_errors", TSPP2_S_RW, dentry, base + TSPP2_FILTER_PES_ERRORS(i), &fops_iomem_x32); debugfs_create_u32( "stat_sc_go_high", S_IRUGO | S_IWUSR | S_IWGRP, dentry, &device->irq_stats.ctx[i].sc_go_high); debugfs_create_u32( "stat_sc_go_low", S_IRUGO | S_IWUSR | S_IWGRP, dentry, &device->irq_stats.ctx[i].sc_go_low); } } dir = debugfs_create_dir("filters", device->debugfs_entry); for (i = 0; i < TSPP2_NUM_HW_FILTERS; i++) { snprintf(name, 80, "filter%03i", i); dentry = debugfs_create_dir(name, dir); if (dentry) { debugfs_create_file("filter_entry0", TSPP2_S_RW, dentry, base + TSPP2_FILTER_ENTRY0(i), &fops_iomem_x32); debugfs_create_file("filter_entry1", TSPP2_S_RW, dentry, base + TSPP2_FILTER_ENTRY1(i), &fops_iomem_x32); for (j = 0; j < TSPP2_MAX_OPS_PER_FILTER; j++) { snprintf(name, 80, "opcode%02i", j); debugfs_create_file(name, TSPP2_S_RW, dentry, base + TSPP2_OPCODE(i, j), &fops_iomem_x32); } } } dir = debugfs_create_dir("mem_sources", device->debugfs_entry); for (i = 0; i < TSPP2_NUM_MEM_INPUTS; i++) { snprintf(name, 80, "mem_src%i", i); dentry = debugfs_create_dir(name, dir); if (dentry) { debugfs_create_u32( "stat_read_failure", S_IRUGO | S_IWUSR | S_IWGRP, dentry, &device->irq_stats.src[i].read_failure); debugfs_create_u32( "stat_flow_control_stall", S_IRUGO | S_IWUSR | S_IWGRP, dentry, &device->irq_stats.src[i].flow_control_stall); } } dir = debugfs_create_dir("key_tables", device->debugfs_entry); for (i = 0; i < TSPP2_NUM_KEYTABLES; i++) { snprintf(name, 80, "key_table%02i", i); dentry = debugfs_create_dir(name, dir); if (dentry) { debugfs_create_u32( "stat_key_not_ready", S_IRUGO | S_IWUSR | S_IWGRP, dentry, &device->irq_stats.kt[i].key_not_ready); } } dir = debugfs_create_dir("pipes", device->debugfs_entry); for (i = 0; i < TSPP2_NUM_PIPES; i++) { snprintf(name, 80, "pipe%02i", i); dentry = debugfs_create_dir(name, dir); if (dentry) { debugfs_create_file("threshold", TSPP2_S_RW, dentry, base + TSPP2_PIPE_THRESH_CONFIG(i), &fops_iomem_x32); debugfs_create_file("last_address", S_IRUGO, dentry, base + TSPP2_PIPE_LAST_ADDRESS(i), &fops_iomem_x32); debugfs_create_file("data_not_sent", S_IRUGO, dentry, base + TSPP2_DATA_NOT_SENT_ON_PIPE(i), &fops_iomem_x32); debugfs_create_u32( "stat_unexpected_reset", S_IRUGO | S_IWUSR | S_IWGRP, dentry, &device->irq_stats.pipe[i].unexpected_reset); debugfs_create_u32( "stat_qsb_response_error", S_IRUGO | S_IWUSR | S_IWGRP, dentry, &device->irq_stats.pipe[i].qsb_response_error); debugfs_create_u32( "stat_wrong_pipe_direction", S_IRUGO | S_IWUSR | S_IWGRP, dentry, &device->irq_stats.pipe[i]. wrong_pipe_direction); } } dir = debugfs_create_dir("indexing_tables", device->debugfs_entry); for (i = 0; i < TSPP2_NUM_INDEXING_TABLES; i++) { snprintf(name, 80, "indexing_table%i", i); dentry = debugfs_create_dir(name, dir); if (dentry) { debugfs_create_file("prefix", TSPP2_S_RW, dentry, base + TSPP2_INDEX_TABLE_PREFIX(i), &fops_iomem_x32); debugfs_create_file("mask", TSPP2_S_RW, dentry, base + TSPP2_INDEX_TABLE_PREFIX_MASK(i), &fops_iomem_x32); debugfs_create_file("parameters", TSPP2_S_RW, dentry, base + TSPP2_INDEX_TABLE_PARAMS(i), &fops_iomem_x32); for (j = 0; j < TSPP2_NUM_INDEXING_PATTERNS; j++) { snprintf(name, 80, "pattern_%02i", j); debugfs_create_file(name, TSPP2_S_RW, dentry, base + TSPP2_INDEX_TABLE_PATTEREN(i, j), &fops_iomem_x32); snprintf(name, 80, "mask_%02i", j); debugfs_create_file(name, TSPP2_S_RW, dentry, base + TSPP2_INDEX_TABLE_MASK(i, j), &fops_iomem_x32); } } } dir = debugfs_create_dir("software", device->debugfs_entry); debugfs_create_file("device", S_IRUGO, dir, device, &dbgfs_tspp2_device_fops); dentry = debugfs_create_dir("filters", dir); if (dentry) { for (i = 0; i < TSPP2_NUM_AVAIL_FILTERS; i++) { snprintf(name, 20, "filter%03i", i); debugfs_create_file(name, S_IRUGO, dentry, &(device->filters[i]), &dbgfs_filter_fops); } } dentry = debugfs_create_dir("pipes", dir); if (dentry) { for (i = 0; i < TSPP2_NUM_PIPES; i++) { snprintf(name, 20, "pipe%02i", i); debugfs_create_file(name, S_IRUGO, dentry, &(device->pipes[i]), &dbgfs_pipe_fops); } } dentry = debugfs_create_dir("sources", dir); if (dentry) { for (i = 0; i < TSPP2_NUM_TSIF_INPUTS; i++) { snprintf(name, 20, "tsif%d", i); debugfs_create_file(name, S_IRUGO, dentry, &(device->tsif_sources[i]), &dbgfs_src_fops); } for (i = 0; i < TSPP2_NUM_MEM_INPUTS; i++) { snprintf(name, 20, "mem%d", i); debugfs_create_file(name, S_IRUGO, dentry, &(device->mem_sources[i]), &dbgfs_src_fops); } } } /** * tspp2_debugfs_exit() - TSPP2 device debugfs teardown. * * @device: TSPP2 device. */ static void tspp2_debugfs_exit(struct tspp2_device *device) { debugfs_remove_recursive(device->debugfs_entry); device->debugfs_entry = NULL; } /** * tspp2_tsif_start() - Start TSIF device HW. * * @tsif_device: TSIF device. * * Return 0 on success, error value otherwise. */ static int tspp2_tsif_start(struct tspp2_tsif_device *tsif_device) { u32 ctl; if (tsif_device->ref_count > 0) return 0; ctl = (TSIF_STS_CTL_EN_IRQ | TSIF_STS_CTL_EN_DM | TSIF_STS_CTL_PACK_AVAIL | TSIF_STS_CTL_OVERFLOW | TSIF_STS_CTL_LOST_SYNC | TSIF_STS_CTL_TIMEOUT | TSIF_STS_CTL_PARALLEL); if (tsif_device->clock_inverse) ctl |= TSIF_STS_CTL_INV_CLOCK; if (tsif_device->data_inverse) ctl |= TSIF_STS_CTL_INV_DATA; if (tsif_device->sync_inverse) ctl |= TSIF_STS_CTL_INV_SYNC; if (tsif_device->enable_inverse) ctl |= TSIF_STS_CTL_INV_ENABLE; switch (tsif_device->mode) { case TSPP2_TSIF_MODE_LOOPBACK: ctl |= TSIF_STS_CTL_EN_NULL | TSIF_STS_CTL_EN_ERROR | TSIF_STS_CTL_TEST_MODE; break; case TSPP2_TSIF_MODE_1: ctl |= TSIF_STS_CTL_EN_TIME_LIM | TSIF_STS_CTL_EN_TCR; break; case TSPP2_TSIF_MODE_2: ctl |= TSIF_STS_CTL_EN_TIME_LIM | TSIF_STS_CTL_EN_TCR | TSIF_STS_CTL_MODE_2; break; default: pr_warn("%s: Unknown TSIF mode %d, setting to TSPP2_TSIF_MODE_2\n", __func__, tsif_device->mode); ctl |= TSIF_STS_CTL_EN_TIME_LIM | TSIF_STS_CTL_EN_TCR | TSIF_STS_CTL_MODE_2; break; } writel_relaxed(ctl, tsif_device->base + TSPP2_TSIF_STS_CTL); writel_relaxed(tsif_device->time_limit, tsif_device->base + TSPP2_TSIF_TIME_LIMIT); wmb(); writel_relaxed(ctl | TSIF_STS_CTL_START, tsif_device->base + TSPP2_TSIF_STS_CTL); wmb(); ctl = readl_relaxed(tsif_device->base + TSPP2_TSIF_STS_CTL); if (ctl & TSIF_STS_CTL_START) tsif_device->ref_count++; return (ctl & TSIF_STS_CTL_START) ? 0 : -EBUSY; } static int tspp2_vbif_clock_start(struct tspp2_device *device) { int ret; if (device->tspp2_vbif_clk) { ret = clk_prepare_enable(device->tspp2_vbif_clk); if (ret) { pr_err("%s: Can't start tspp2_vbif_clk\n", __func__); return ret; } } if (device->vbif_ahb_clk) { ret = clk_prepare_enable(device->vbif_ahb_clk); if (ret) { pr_err("%s: Can't start vbif_ahb_clk\n", __func__); goto disable_vbif_tspp2; } } if (device->vbif_axi_clk) { ret = clk_prepare_enable(device->vbif_axi_clk); if (ret) { pr_err("%s: Can't start vbif_ahb_clk\n", __func__); goto disable_vbif_ahb; } } return 0; disable_vbif_ahb: if (device->vbif_ahb_clk) clk_disable_unprepare(device->vbif_ahb_clk); disable_vbif_tspp2: if (device->tspp2_vbif_clk) clk_disable_unprepare(device->tspp2_vbif_clk); return ret; } static void tspp2_vbif_clock_stop(struct tspp2_device *device) { if (device->tspp2_vbif_clk) clk_disable_unprepare(device->tspp2_vbif_clk); if (device->vbif_ahb_clk) clk_disable_unprepare(device->vbif_ahb_clk); if (device->vbif_axi_clk) clk_disable_unprepare(device->vbif_axi_clk); } /** * tspp2_tsif_stop() - Stop TSIF device HW. * * @tsif_device: TSIF device. */ static void tspp2_tsif_stop(struct tspp2_tsif_device *tsif_device) { if (tsif_device->ref_count == 0) return; tsif_device->ref_count--; if (tsif_device->ref_count == 0) { writel_relaxed(TSIF_STS_CTL_STOP, tsif_device->base + TSPP2_TSIF_STS_CTL); /* * The driver assumes that after this point the TSIF is stopped, * so a memory barrier is required to allow * further register writes. */ wmb(); } } /* Clock functions */ static int tspp2_reg_clock_start(struct tspp2_device *device) { int rc; if (device->tspp2_ahb_clk && clk_prepare_enable(device->tspp2_ahb_clk) != 0) { pr_err("%s: Can't start tspp2_ahb_clk\n", __func__); return -EBUSY; } if (device->tspp2_core_clk && clk_prepare_enable(device->tspp2_core_clk) != 0) { pr_err("%s: Can't start tspp2_core_clk\n", __func__); if (device->tspp2_ahb_clk) clk_disable_unprepare(device->tspp2_ahb_clk); return -EBUSY; } /* Request minimal bandwidth on the bus, required for register access */ if (device->bus_client) { rc = msm_bus_scale_client_update_request(device->bus_client, 1); if (rc) { pr_err("%s: Can't enable bus\n", __func__); if (device->tspp2_core_clk) clk_disable_unprepare(device->tspp2_core_clk); if (device->tspp2_ahb_clk) clk_disable_unprepare(device->tspp2_ahb_clk); return -EBUSY; } } return 0; } static int tspp2_reg_clock_stop(struct tspp2_device *device) { /* Minimize bandwidth bus voting */ if (device->bus_client) msm_bus_scale_client_update_request(device->bus_client, 0); if (device->tspp2_core_clk) clk_disable_unprepare(device->tspp2_core_clk); if (device->tspp2_ahb_clk) clk_disable_unprepare(device->tspp2_ahb_clk); return 0; } /** * tspp2_clock_start() - Enable the required TSPP2 clocks * * @device: The TSPP2 device. * * Return 0 on success, error value otherwise. */ static int tspp2_clock_start(struct tspp2_device *device) { int tspp2_ahb_clk = 0; int tspp2_core_clk = 0; int tspp2_vbif_clk = 0; int tspp2_klm_ahb_clk = 0; int tsif_ref_clk = 0; if (device == NULL) { pr_err("%s: Can't start clocks, invalid device\n", __func__); return -EINVAL; } if (device->tspp2_ahb_clk) { if (clk_prepare_enable(device->tspp2_ahb_clk) != 0) { pr_err("%s: Can't start tspp2_ahb_clk\n", __func__); goto err_clocks; } tspp2_ahb_clk = 1; } if (device->tspp2_core_clk) { if (clk_prepare_enable(device->tspp2_core_clk) != 0) { pr_err("%s: Can't start tspp2_core_clk\n", __func__); goto err_clocks; } tspp2_core_clk = 1; } if (device->tspp2_klm_ahb_clk) { if (clk_prepare_enable(device->tspp2_klm_ahb_clk) != 0) { pr_err("%s: Can't start tspp2_klm_ahb_clk\n", __func__); goto err_clocks; } tspp2_klm_ahb_clk = 1; } if (device->tsif_ref_clk) { if (clk_prepare_enable(device->tsif_ref_clk) != 0) { pr_err("%s: Can't start tsif_ref_clk\n", __func__); goto err_clocks; } tsif_ref_clk = 1; } /* Request Max bandwidth on the bus, required for full operation */ if (device->bus_client && msm_bus_scale_client_update_request(device->bus_client, 2)) { pr_err("%s: Can't enable bus\n", __func__); goto err_clocks; } return 0; err_clocks: if (tspp2_ahb_clk) clk_disable_unprepare(device->tspp2_ahb_clk); if (tspp2_core_clk) clk_disable_unprepare(device->tspp2_core_clk); if (tspp2_vbif_clk) clk_disable_unprepare(device->tspp2_vbif_clk); if (tspp2_klm_ahb_clk) clk_disable_unprepare(device->tspp2_klm_ahb_clk); if (tsif_ref_clk) clk_disable_unprepare(device->tsif_ref_clk); return -EBUSY; } /** * tspp2_clock_stop() - Disable TSPP2 clocks * * @device: The TSPP2 device. */ static void tspp2_clock_stop(struct tspp2_device *device) { if (device == NULL) { pr_err("%s: Can't stop clocks, invalid device\n", __func__); return; } /* Minimize bandwidth bus voting */ if (device->bus_client) msm_bus_scale_client_update_request(device->bus_client, 0); if (device->tsif_ref_clk) clk_disable_unprepare(device->tsif_ref_clk); if (device->tspp2_klm_ahb_clk) clk_disable_unprepare(device->tspp2_klm_ahb_clk); if (device->tspp2_core_clk) clk_disable_unprepare(device->tspp2_core_clk); if (device->tspp2_ahb_clk) clk_disable_unprepare(device->tspp2_ahb_clk); } /** * tspp2_filter_counters_reset() - Reset a filter's HW counters. * * @device: TSPP2 device. * @index: Filter context index. Note counters are based on the context * index and not on the filter HW index. */ static void tspp2_filter_counters_reset(struct tspp2_device *device, u32 index) { /* Reset filter counters */ writel_relaxed(0, device->base + TSPP2_FILTER_TSP_SYNC_ERROR(index)); writel_relaxed(0, device->base + TSPP2_FILTER_ERRED_TSP(index)); writel_relaxed(0, device->base + TSPP2_FILTER_DISCONTINUITIES(index)); writel_relaxed(0, device->base + TSPP2_FILTER_SCRAMBLING_BITS_DISCARD(index)); writel_relaxed(0, device->base + TSPP2_FILTER_TSP_TOTAL_NUM(index)); writel_relaxed(0, device->base + TSPP2_FILTER_DISCONT_INDICATOR(index)); writel_relaxed(0, device->base + TSPP2_FILTER_TSP_NO_PAYLOAD(index)); writel_relaxed(0, device->base + TSPP2_FILTER_TSP_DUPLICATE(index)); writel_relaxed(0, device->base + TSPP2_FILTER_KEY_FETCH_FAILURE(index)); writel_relaxed(0, device->base + TSPP2_FILTER_DROPPED_PCR(index)); writel_relaxed(0, device->base + TSPP2_FILTER_PES_ERRORS(index)); } /** * tspp2_global_hw_reset() - Reset TSPP2 device registers to a default state. * * @device: TSPP2 device. * @enable_intr: Enable specific interrupts or disable them. * * A helper function called from probe() and remove(), this function resets both * TSIF devices' SW structures and verifies the TSIF HW is stopped. It resets * TSPP2 registers to appropriate default values and makes sure to disable * all sources, filters etc. Finally, it clears all interrupts and unmasks * the "important" interrupts. * * Return 0 on success, error value otherwise. */ static int tspp2_global_hw_reset(struct tspp2_device *device, int enable_intr) { int i, n; unsigned long rate_in_hz = 0; u32 global_irq_en = 0; if (!device) { pr_err("%s: NULL device\n", __func__); return -ENODEV; } /* Stop TSIF devices */ for (i = 0; i < TSPP2_NUM_TSIF_INPUTS; i++) { device->tsif_devices[i].hw_index = i; device->tsif_devices[i].dev = device; device->tsif_devices[i].mode = TSPP2_TSIF_MODE_2; device->tsif_devices[i].clock_inverse = 0; device->tsif_devices[i].data_inverse = 0; device->tsif_devices[i].sync_inverse = 0; device->tsif_devices[i].enable_inverse = 0; device->tsif_devices[i].stat_pkt_write_err = 0; device->tsif_devices[i].stat_pkt_read_err = 0; device->tsif_devices[i].stat_overflow = 0; device->tsif_devices[i].stat_lost_sync = 0; device->tsif_devices[i].stat_timeout = 0; device->tsif_devices[i].time_limit = TSPP2_TSIF_DEF_TIME_LIMIT; /* Set ref_count to 1 to allow stopping HW */ device->tsif_devices[i].ref_count = 1; /* This will reset ref_count to 0 */ tspp2_tsif_stop(&device->tsif_devices[i]); } /* Reset indexing table registers */ for (i = 0; i < TSPP2_NUM_INDEXING_TABLES; i++) { writel_relaxed(0, device->base + TSPP2_INDEX_TABLE_PREFIX(i)); writel_relaxed(0, device->base + TSPP2_INDEX_TABLE_PREFIX_MASK(i)); for (n = 0; n < TSPP2_NUM_INDEXING_PATTERNS; n++) { writel_relaxed(0, device->base + TSPP2_INDEX_TABLE_PATTEREN(i, n)); writel_relaxed(0, device->base + TSPP2_INDEX_TABLE_MASK(i, n)); } /* Set number of patterns to 0, prefix size to 4 by default */ writel_relaxed(0x00000400, device->base + TSPP2_INDEX_TABLE_PARAMS(i)); } /* Disable TSIF inputs. Set mode of operation to 16 batches */ for (i = 0; i < TSPP2_NUM_TSIF_INPUTS; i++) writel_relaxed((0x1 << TSIF_INPUT_SRC_CONFIG_16_BATCHES_OFFS), device->base + TSPP2_TSIF_INPUT_SRC_CONFIG(i)); /* Reset source related registers and performance counters */ for (i = 0; i < TSPP2_NUM_ALL_INPUTS; i++) { writel_relaxed(0, device->base + TSPP2_SRC_DEST_PIPES(i)); /* Set source configuration to default values */ writel_relaxed(TSPP2_DEFAULT_SRC_CONFIG, device->base + TSPP2_SRC_CONFIG(i)); } writel_relaxed(0x000003FF, device->base + TSPP2_SRC_TOTAL_TSP_RESET); writel_relaxed(0x000003FF, device->base + TSPP2_SRC_FILTERED_OUT_TSP_RESET); /* Reset all contexts, each register handles 32 contexts */ for (i = 0; i < 4; i++) { writel_relaxed(0xFFFFFFFF, device->base + TSPP2_TSP_CONTEXT_RESET(i)); writel_relaxed(0xFFFFFFFF, device->base + TSPP2_PES_CONTEXT_RESET(i)); writel_relaxed(0xFFFFFFFF, device->base + TSPP2_INDEXING_CONTEXT_RESET(i)); } for (i = 0; i < TSPP2_NUM_HW_FILTERS; i++) { /* * Reset operations: put exit operation in all filter operations */ for (n = 0; n < TSPP2_MAX_OPS_PER_FILTER; n++) { writel_relaxed(TSPP2_OPCODE_EXIT, device->base + TSPP2_OPCODE(i, n)); } /* Disable all HW filters */ writel_relaxed(0, device->base + TSPP2_FILTER_ENTRY0(i)); writel_relaxed(0, device->base + TSPP2_FILTER_ENTRY1(i)); } for (i = 0; i < TSPP2_NUM_CONTEXTS; i++) { /* Reset filter context-based counters */ tspp2_filter_counters_reset(device, i); } /* * Disable memory inputs. Set mode of operation to 16 batches. * Configure last batch to be associated with all memory input sources, * and add a filter to match all PIDs and drop the TS packets in the * last HW filter entry. Use the last context for this filter. */ for (i = 0; i < TSPP2_NUM_MEM_INPUTS; i++) writel_relaxed(TSPP2_DEFAULT_MEM_SRC_CONFIG, device->base + TSPP2_MEM_INPUT_SRC_CONFIG(i)); writel_relaxed(((TSPP2_NUM_CONTEXTS - 1) << FILTER_ENTRY1_CONTEXT_OFFS), device->base + TSPP2_FILTER_ENTRY1((TSPP2_NUM_HW_FILTERS - 1))); writel_relaxed((0x1 << FILTER_ENTRY0_EN_OFFS), device->base + TSPP2_FILTER_ENTRY0((TSPP2_NUM_HW_FILTERS - 1))); /* Reset pipe registers */ for (i = 0; i < TSPP2_NUM_PIPES; i++) writel_relaxed(0xFFFF, device->base + TSPP2_PIPE_THRESH_CONFIG(i)); writel_relaxed(0, device->base + TSPP2_PIPE_SECURITY); writel_relaxed(0x7FFFFFFF, device->base + TSPP2_DATA_NOT_SENT_ON_PIPE_RESET); /* Set global configuration to default values */ /* * Default: minimum time between PCRs = 50msec, STC offset is 0, * transmit PCR on discontinuity. */ writel_relaxed(0x00000432, device->base + TSPP2_PCR_GLOBAL_CONFIG); /* Set correct value according to TSPP2 clock: */ if (device->tspp2_core_clk) { rate_in_hz = clk_get_rate(device->tspp2_core_clk); writel_relaxed((rate_in_hz / MSEC_PER_SEC), device->base + TSPP2_CLK_TO_PCR_TIME_UNIT); } else { writel_relaxed(0x00000000, device->base + TSPP2_CLK_TO_PCR_TIME_UNIT); } writel_relaxed(0x00000000, device->base + TSPP2_DESC_WAIT_TIMEOUT); /* Clear all global interrupts */ writel_relaxed(0xFFFF000F, device->base + TSPP2_GLOBAL_IRQ_CLEAR); writel_relaxed(0x7FFFFFFF, device->base + TSPP2_UNEXPECTED_RST_IRQ_CLEAR); writel_relaxed(0x7FFFFFFF, device->base + TSPP2_WRONG_PIPE_DIR_IRQ_CLEAR); writel_relaxed(0x7FFFFFFF, device->base + TSPP2_QSB_RESPONSE_ERROR_IRQ_CLEAR); writel_relaxed(0xFFFFFFFF, device->base + TSPP2_KEY_NOT_READY_IRQ_CLEAR); /* * Global interrupts configuration: * Flow Control (per memory source): Disabled * Read Failure (per memory source): Enabled * SC_GO_LOW (aggregate): Enabled * SC_GO_HIGH (aggregate): Enabled * Wrong Pipe Direction (aggregate): Enabled * QSB Response Error (aggregate): Enabled * Unexpected Reset (aggregate): Enabled * Key Not Ready (aggregate): Disabled * Op Encrypt Level Error: Enabled * PES No Sync: Disabled (module parameter) * TSP Invalid Length: Disabled (module parameter) * TSP Invalid AF Control: Disabled (module parameter) */ global_irq_en = 0x00FF03E8; if (tspp2_en_invalid_af_ctrl) global_irq_en |= (0x1 << GLOBAL_IRQ_TSP_INVALID_AF_OFFS); if (tspp2_en_invalid_af_length) global_irq_en |= (0x1 << GLOBAL_IRQ_TSP_INVALID_LEN_OFFS); if (tspp2_en_pes_no_sync) global_irq_en |= (0x1 << GLOBAL_IRQ_PES_NO_SYNC_OFFS); if (enable_intr) writel_relaxed(global_irq_en, device->base + TSPP2_GLOBAL_IRQ_ENABLE); else writel_relaxed(0, device->base + TSPP2_GLOBAL_IRQ_ENABLE); if (enable_intr) { /* Enable all pipe related interrupts */ writel_relaxed(0x7FFFFFFF, device->base + TSPP2_UNEXPECTED_RST_IRQ_ENABLE); writel_relaxed(0x7FFFFFFF, device->base + TSPP2_WRONG_PIPE_DIR_IRQ_ENABLE); writel_relaxed(0x7FFFFFFF, device->base + TSPP2_QSB_RESPONSE_ERROR_IRQ_ENABLE); } else { /* Disable all pipe related interrupts */ writel_relaxed(0, device->base + TSPP2_UNEXPECTED_RST_IRQ_ENABLE); writel_relaxed(0, device->base + TSPP2_WRONG_PIPE_DIR_IRQ_ENABLE); writel_relaxed(0, device->base + TSPP2_QSB_RESPONSE_ERROR_IRQ_ENABLE); } /* Disable Key Ladder interrupts */ writel_relaxed(0, device->base + TSPP2_KEY_NOT_READY_IRQ_ENABLE); /* * Clear and disable scrambling control interrupts. * Each register handles 32 filters. */ for (i = 0; i < 4; i++) { writel_relaxed(0xFFFFFFFF, device->base + TSPP2_SC_GO_HIGH_CLEAR(i)); writel_relaxed(0, device->base + TSPP2_SC_GO_HIGH_ENABLE(i)); writel_relaxed(0xFFFFFFFF, device->base + TSPP2_SC_GO_LOW_CLEAR(i)); writel_relaxed(0, device->base + TSPP2_SC_GO_LOW_ENABLE(i)); } dev_dbg(device->dev, "%s: successful\n", __func__); return 0; } /** * tspp2_event_work_handler - Handle the work - invoke the user callback. * * @work: The work information. */ static void tspp2_event_work_handler(struct work_struct *work) { struct tspp2_event_work *event_work = container_of(work, struct tspp2_event_work, work); struct tspp2_event_work cb_info = *event_work; if (mutex_lock_interruptible(&event_work->device->mutex)) return; list_add_tail(&event_work->link, &event_work->device->free_work_list); mutex_unlock(&event_work->device->mutex); /* * Must run callback with tspp2 device mutex unlocked, * as callback might call tspp2 driver API and cause a deadlock. */ if (cb_info.callback) cb_info.callback(cb_info.cookie, cb_info.event_bitmask); } /** * tspp2_device_initialize() - Initialize TSPP2 device SW structures. * * @device: TSPP2 device * * Initialize the required SW structures and fields in the TSPP2 device, * including ION client creation, BAM registration, debugfs initialization etc. * * Return 0 on success, error value otherwise. */ static int tspp2_device_initialize(struct tspp2_device *device) { int i, ret; if (!device) { pr_err("%s: NULL device\n", __func__); return -ENODEV; } /* Register BAM */ device->bam_props.summing_threshold = 0x10; device->bam_props.irq = device->bam_irq; device->bam_props.manage = SPS_BAM_MGR_LOCAL; ret = sps_register_bam_device(&device->bam_props, &device->bam_handle); if (ret) { pr_err("%s: failed to register BAM\n", __func__); return ret; } ret = sps_device_reset(device->bam_handle); if (ret) { sps_deregister_bam_device(device->bam_handle); pr_err("%s: error resetting BAM\n", __func__); return ret; } spin_lock_init(&device->spinlock); wakeup_source_init(&device->wakeup_src, dev_name(&device->pdev->dev)); for (i = 0; i < TSPP2_NUM_TSIF_INPUTS; i++) tspp2_tsif_debugfs_init(&device->tsif_devices[i]); /* * The device structure was allocated using devm_kzalloc() so * the memory was initialized to zero. We don't need to specifically set * fields to zero, then. We only set the fields we need to, such as * batch_id. */ for (i = 0; i < TSPP2_NUM_BATCHES; i++) { device->batches[i].batch_id = i; device->batches[i].src = NULL; INIT_LIST_HEAD(&device->batches[i].link); } /* * We set the device back-pointer in the sources, filters and pipes * databases here, so that back-pointer is always valid (instead of * setting it when opening a source, filter or pipe). */ for (i = 0; i < TSPP2_NUM_TSIF_INPUTS; i++) device->tsif_sources[i].device = device; for (i = 0; i < TSPP2_NUM_MEM_INPUTS; i++) device->mem_sources[i].device = device; for (i = 0; i < TSPP2_NUM_AVAIL_FILTERS; i++) device->filters[i].device = device; for (i = 0; i < TSPP2_NUM_PIPES; i++) device->pipes[i].device = device; /* * Note: tsif_devices are initialized as part of tspp2_global_hw_reset() */ device->work_queue = create_singlethread_workqueue(dev_name(device->dev)); INIT_LIST_HEAD(&device->free_work_list); for (i = 0; i < TSPP2_NUM_EVENT_WORK_ELEMENTS; i++) { device->work_pool[i].device = device; device->work_pool[i].callback = 0; device->work_pool[i].cookie = 0; device->work_pool[i].event_bitmask = 0; INIT_LIST_HEAD(&device->work_pool[i].link); INIT_WORK(&device->work_pool[i].work, tspp2_event_work_handler); list_add_tail(&device->work_pool[i].link, &device->free_work_list); } device->event_callback = NULL; device->event_cookie = NULL; dev_dbg(device->dev, "%s: successful\n", __func__); return 0; } /** * tspp2_device_uninitialize() - TSPP2 device teardown and cleanup. * * @device: TSPP2 device * * TSPP2 device teardown: debugfs removal, BAM de-registration etc. * Return 0 on success, error value otherwise. */ static int tspp2_device_uninitialize(struct tspp2_device *device) { int i; if (!device) { pr_err("%s: NULL device\n", __func__); return -ENODEV; } destroy_workqueue(device->work_queue); for (i = 0; i < TSPP2_NUM_TSIF_INPUTS; i++) tspp2_tsif_debugfs_exit(&device->tsif_devices[i]); /* Need to start clocks for BAM de-registration */ if (pm_runtime_get_sync(device->dev) >= 0) { sps_deregister_bam_device(device->bam_handle); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); } wakeup_source_trash(&device->wakeup_src); dev_dbg(device->dev, "%s: successful\n", __func__); return 0; } /** * tspp2_src_disable_internal() - Helper function to disable a source. * * @src: Source to disable. * * Return 0 on success, error value otherwise. */ static int tspp2_src_disable_internal(struct tspp2_src *src) { u32 reg; if (!src) { pr_err("%s: Invalid source handle\n", __func__); return -EINVAL; } if (!src->opened) { pr_err("%s: Source not opened\n", __func__); return -EINVAL; } if (!src->enabled) { pr_warn("%s: Source already disabled\n", __func__); return 0; } if ((src->input == TSPP2_INPUT_TSIF0) || (src->input == TSPP2_INPUT_TSIF1)) { reg = readl_relaxed(src->device->base + TSPP2_TSIF_INPUT_SRC_CONFIG(src->input)); reg &= ~(0x1 << TSIF_INPUT_SRC_CONFIG_INPUT_EN_OFFS); writel_relaxed(reg, src->device->base + TSPP2_TSIF_INPUT_SRC_CONFIG(src->input)); tspp2_tsif_stop(&src->device->tsif_devices[src->input]); } else { reg = readl_relaxed(src->device->base + TSPP2_MEM_INPUT_SRC_CONFIG(src->hw_index)); reg &= ~(0x1 << MEM_INPUT_SRC_CONFIG_INPUT_EN_OFFS); writel_relaxed(reg, src->device->base + TSPP2_MEM_INPUT_SRC_CONFIG(src->hw_index)); } /* * HW requires we wait for up to 2ms here before closing the pipes * attached to (and used by) this source */ udelay(TSPP2_HW_DELAY_USEC); src->enabled = 0; src->device->num_enabled_sources--; if (src->device->num_enabled_sources == 0) { __pm_relax(&src->device->wakeup_src); tspp2_clock_stop(src->device); } return 0; } /* TSPP2 device open / close API */ /** * tspp2_device_open() - Open a TSPP2 device for use. * * @dev_id: TSPP2 device ID. * * Return 0 on success, error value otherwise. */ int tspp2_device_open(u32 dev_id) { int rc; u32 reg = 0; struct tspp2_device *device; if (dev_id >= TSPP2_NUM_DEVICES) { pr_err("%s: Invalid device ID %d\n", __func__, dev_id); return -ENODEV; } device = tspp2_devices[dev_id]; if (!device) { pr_err("%s: Invalid device\n", __func__); return -ENODEV; } if (mutex_lock_interruptible(&device->mutex)) return -ERESTARTSYS; if (device->opened) { pr_err("%s: Device already opened\n", __func__); mutex_unlock(&device->mutex); return -EPERM; } /* Enable power regulator */ rc = regulator_enable(device->gdsc); if (rc) goto err_mutex_unlock; /* Reset TSPP2 core */ clk_reset(device->tspp2_core_clk, CLK_RESET_ASSERT); udelay(10); clk_reset(device->tspp2_core_clk, CLK_RESET_DEASSERT); /* Start HW clocks before accessing registers */ rc = tspp2_reg_clock_start(device); if (rc) goto err_regulator_disable; rc = tspp2_global_hw_reset(device, 1); if (rc) goto err_stop_clocks; rc = tspp2_device_initialize(device); if (rc) goto err_stop_clocks; reg = readl_relaxed(device->base + TSPP2_VERSION); pr_info("TSPP2 HW Version: Major = %d, Minor = %d, Step = %d\n", ((reg & 0xF0000000) >> VERSION_MAJOR_OFFS), ((reg & 0x0FFF0000) >> VERSION_MINOR_OFFS), ((reg & 0x0000FFFF) >> VERSION_STEP_OFFS)); /* Stop HW clocks to save power */ tspp2_reg_clock_stop(device); /* Enable runtime power management */ pm_runtime_set_autosuspend_delay(device->dev, MSEC_PER_SEC); pm_runtime_use_autosuspend(device->dev); pm_runtime_enable(device->dev); device->opened = 1; mutex_unlock(&device->mutex); dev_dbg(device->dev, "%s: successful\n", __func__); return 0; err_stop_clocks: tspp2_reg_clock_stop(device); err_regulator_disable: regulator_disable(device->gdsc); err_mutex_unlock: mutex_unlock(&device->mutex); return rc; } EXPORT_SYMBOL(tspp2_device_open); /** * tspp2_device_close() - Close a TSPP2 device. * * @dev_id: TSPP2 device ID. * * Return 0 on success, error value otherwise. */ int tspp2_device_close(u32 dev_id) { int i; int ret = 0; struct tspp2_device *device; if (dev_id >= TSPP2_NUM_DEVICES) { pr_err("%s: Invalid device ID %d\n", __func__, dev_id); return -ENODEV; } device = tspp2_devices[dev_id]; if (!device) { pr_err("%s: Invalid device\n", __func__); return -ENODEV; } ret = pm_runtime_get_sync(device->dev); if (ret < 0) return ret; mutex_lock(&device->mutex); if (!device->opened) { pr_err("%s: Device already closed\n", __func__); mutex_unlock(&device->mutex); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); return -EPERM; } device->opened = 0; /* * In case the user has not disabled all the enabled sources, we need * to disable them here, specifically in order to call tspp2_clock_stop, * because the calls to enable and disable the clocks should be * symmetrical (otherwise we cannot put the clocks). */ for (i = 0; i < TSPP2_NUM_TSIF_INPUTS; i++) { if (device->tsif_sources[i].enabled) tspp2_src_disable_internal(&device->tsif_sources[i]); } for (i = 0; i < TSPP2_NUM_MEM_INPUTS; i++) { if (device->mem_sources[i].enabled) tspp2_src_disable_internal(&device->mem_sources[i]); } /* bring HW registers back to a known state */ tspp2_global_hw_reset(device, 0); tspp2_device_uninitialize(device); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); /* Disable runtime power management */ pm_runtime_disable(device->dev); pm_runtime_set_suspended(device->dev); if (regulator_disable(device->gdsc)) pr_err("%s: Error disabling power regulator\n", __func__); mutex_unlock(&device->mutex); dev_dbg(device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_device_close); /* Global configuration API */ /** * tspp2_config_set() - Set device global configuration. * * @dev_id: TSPP2 device ID. * @cfg: TSPP2 global configuration parameters to set. * * Return 0 on success, error value otherwise. */ int tspp2_config_set(u32 dev_id, const struct tspp2_config *cfg) { int ret; u32 reg = 0; struct tspp2_device *device; if (dev_id >= TSPP2_NUM_DEVICES) { pr_err("%s: Invalid device ID %d\n", __func__, dev_id); return -ENODEV; } if (!cfg) { pr_err("%s: NULL configuration\n", __func__); return -EINVAL; } if (cfg->stc_byte_offset > 3) { pr_err("%s: Invalid stc_byte_offset %d, valid values are 0 - 3\n", __func__, cfg->stc_byte_offset); return -EINVAL; } device = tspp2_devices[dev_id]; if (!device) { pr_err("%s: Invalid device\n", __func__); return -ENODEV; } ret = pm_runtime_get_sync(device->dev); if (ret < 0) return ret; if (mutex_lock_interruptible(&device->mutex)) { pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); return -ERESTARTSYS; } if (!device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&device->mutex); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); return -EPERM; } if (cfg->pcr_on_discontinuity) reg |= (0x1 << PCR_GLOBAL_CONFIG_PCR_ON_DISCONT_OFFS); reg |= (cfg->stc_byte_offset << PCR_GLOBAL_CONFIG_STC_OFFSET_OFFS); reg |= (cfg->min_pcr_interval << PCR_GLOBAL_CONFIG_PCR_INTERVAL_OFFS); writel_relaxed(reg, device->base + TSPP2_PCR_GLOBAL_CONFIG); mutex_unlock(&device->mutex); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); dev_dbg(device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_config_set); /** * tspp2_config_get() - Get current global configuration. * * @dev_id: TSPP2 device ID. * @cfg: TSPP2 global configuration parameters. * * Return 0 on success, error value otherwise. */ int tspp2_config_get(u32 dev_id, struct tspp2_config *cfg) { int ret; u32 reg = 0; struct tspp2_device *device; if (dev_id >= TSPP2_NUM_DEVICES) { pr_err("%s: Invalid device ID %d\n", __func__, dev_id); return -ENODEV; } if (!cfg) { pr_err("%s: NULL configuration\n", __func__); return -EINVAL; } device = tspp2_devices[dev_id]; if (!device) { pr_err("%s: Invalid device\n", __func__); return -ENODEV; } ret = pm_runtime_get_sync(device->dev); if (ret < 0) return ret; if (mutex_lock_interruptible(&device->mutex)) { pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); return -ERESTARTSYS; } if (!device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&device->mutex); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); return -EPERM; } reg = readl_relaxed(device->base + TSPP2_PCR_GLOBAL_CONFIG); cfg->pcr_on_discontinuity = ((reg & PCR_GLOBAL_CONFIG_PCR_ON_DISCONT) >> PCR_GLOBAL_CONFIG_PCR_ON_DISCONT_OFFS); cfg->stc_byte_offset = ((reg & PCR_GLOBAL_CONFIG_STC_OFFSET) >> PCR_GLOBAL_CONFIG_STC_OFFSET_OFFS); cfg->min_pcr_interval = ((reg & PCR_GLOBAL_CONFIG_PCR_INTERVAL) >> PCR_GLOBAL_CONFIG_PCR_INTERVAL_OFFS); mutex_unlock(&device->mutex); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); dev_dbg(device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_config_get); /* Indexing tables API functions */ /** * tspp2_indexing_prefix_set() - Set prefix value and mask of an indexing table. * * @dev_id: TSPP2 device ID. * @table_id: Indexing table ID. * @value: Prefix 4-byte value. * @mask: Prefix 4-byte mask. * * Return 0 on success, error value otherwise. */ int tspp2_indexing_prefix_set(u32 dev_id, u8 table_id, u32 value, u32 mask) { int ret; u32 reg; u8 size = 0; int i; struct tspp2_device *device; struct tspp2_indexing_table *table; if (dev_id >= TSPP2_NUM_DEVICES) { pr_err("%s: Invalid device ID %d\n", __func__, dev_id); return -ENODEV; } if (table_id >= TSPP2_NUM_INDEXING_TABLES) { pr_err("%s: Invalid table ID %d\n", __func__, table_id); return -EINVAL; } device = tspp2_devices[dev_id]; if (!device) { pr_err("%s: Invalid device\n", __func__); return -ENODEV; } ret = pm_runtime_get_sync(device->dev); if (ret < 0) return ret; if (mutex_lock_interruptible(&device->mutex)) { pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); return -ERESTARTSYS; } if (!device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&device->mutex); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); return -EPERM; } table = &device->indexing_tables[table_id]; table->prefix_value = value; table->prefix_mask = mask; /* HW expects values/masks to be written in Big Endian format */ writel_relaxed(cpu_to_be32(value), device->base + TSPP2_INDEX_TABLE_PREFIX(table_id)); writel_relaxed(cpu_to_be32(mask), device->base + TSPP2_INDEX_TABLE_PREFIX_MASK(table_id)); /* Find the actual size of the prefix and set to HW */ reg = readl_relaxed(device->base + TSPP2_INDEX_TABLE_PARAMS(table_id)); for (i = 0; i < 32; i += 8) { if (mask & (0x000000FF << i)) size++; } reg &= ~(0x7 << INDEX_TABLE_PARAMS_PREFIX_SIZE_OFFS); reg |= (size << INDEX_TABLE_PARAMS_PREFIX_SIZE_OFFS); writel_relaxed(reg, device->base + TSPP2_INDEX_TABLE_PARAMS(table_id)); mutex_unlock(&device->mutex); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); dev_dbg(device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_indexing_prefix_set); /** * tspp2_indexing_patterns_add() - Add patterns to an indexing table. * * @dev_id: TSPP2 device ID. * @table_id: Indexing table ID. * @values: An array of 4-byte pattern values. * @masks: An array of corresponding 4-byte masks. * @patterns_num: Number of patterns in the values / masks arrays. * Up to TSPP2_NUM_INDEXING_PATTERNS. * * Return 0 on success, error value otherwise. */ int tspp2_indexing_patterns_add(u32 dev_id, u8 table_id, const u32 *values, const u32 *masks, u8 patterns_num) { int ret; int i; u16 offs = 0; u32 reg; struct tspp2_device *device; struct tspp2_indexing_table *table; if (dev_id >= TSPP2_NUM_DEVICES) { pr_err("%s: Invalid device ID %d\n", __func__, dev_id); return -ENODEV; } if (table_id >= TSPP2_NUM_INDEXING_TABLES) { pr_err("%s: Invalid table ID %d\n", __func__, table_id); return -EINVAL; } if (!values || !masks) { pr_err("%s: NULL values or masks array\n", __func__); return -EINVAL; } device = tspp2_devices[dev_id]; if (!device) { pr_err("%s: Invalid device\n", __func__); return -ENODEV; } ret = pm_runtime_get_sync(device->dev); if (ret < 0) return ret; if (mutex_lock_interruptible(&device->mutex)) { pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); return -ERESTARTSYS; } if (!device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&device->mutex); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); return -EPERM; } table = &device->indexing_tables[table_id]; if ((table->num_valid_entries + patterns_num) > TSPP2_NUM_INDEXING_PATTERNS) { pr_err("%s: Trying to add too many patterns: current number %d, trying to add %d, maximum allowed %d\n", __func__, table->num_valid_entries, patterns_num, TSPP2_NUM_INDEXING_PATTERNS); mutex_unlock(&device->mutex); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); return -EINVAL; } /* There's enough room to add all the requested patterns */ offs = table->num_valid_entries; for (i = 0; i < patterns_num; i++) { table->entry_value[offs + i] = values[i]; table->entry_mask[offs + i] = masks[i]; writel_relaxed(cpu_to_be32(values[i]), device->base + TSPP2_INDEX_TABLE_PATTEREN(table_id, offs + i)); writel_relaxed(cpu_to_be32(masks[i]), device->base + TSPP2_INDEX_TABLE_MASK(table_id, offs + i)); } table->num_valid_entries += patterns_num; reg = readl_relaxed(device->base + TSPP2_INDEX_TABLE_PARAMS(table_id)); reg &= ~(0x1F << INDEX_TABLE_PARAMS_NUM_PATTERNS_OFFS); reg |= (table->num_valid_entries << INDEX_TABLE_PARAMS_NUM_PATTERNS_OFFS); writel_relaxed(reg, device->base + TSPP2_INDEX_TABLE_PARAMS(table_id)); mutex_unlock(&device->mutex); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); dev_dbg(device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_indexing_patterns_add); /** * tspp2_indexing_patterns_clear() - Clear all patterns of an indexing table. * * @dev_id: TSPP2 device ID. * @table_id: Indexing table ID. * * Return 0 on success, error value otherwise. */ int tspp2_indexing_patterns_clear(u32 dev_id, u8 table_id) { int ret; int i; u32 reg; struct tspp2_device *device; struct tspp2_indexing_table *table; if (dev_id >= TSPP2_NUM_DEVICES) { pr_err("%s: Invalid device ID %d\n", __func__, dev_id); return -ENODEV; } if (table_id >= TSPP2_NUM_INDEXING_TABLES) { pr_err("%s: Invalid table ID %d\n", __func__, table_id); return -EINVAL; } device = tspp2_devices[dev_id]; if (!device) { pr_err("%s: Invalid device\n", __func__); return -ENODEV; } ret = pm_runtime_get_sync(device->dev); if (ret < 0) return ret; if (mutex_lock_interruptible(&device->mutex)) { pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); return -ERESTARTSYS; } if (!device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&device->mutex); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); return -EPERM; } table = &device->indexing_tables[table_id]; for (i = 0; i < table->num_valid_entries; i++) { table->entry_value[i] = 0; table->entry_mask[i] = 0; writel_relaxed(0, device->base + TSPP2_INDEX_TABLE_PATTEREN(table_id, i)); writel_relaxed(0, device->base + TSPP2_INDEX_TABLE_MASK(table_id, i)); } table->num_valid_entries = 0; reg = readl_relaxed(device->base + TSPP2_INDEX_TABLE_PARAMS(table_id)); reg &= ~(0x1F << INDEX_TABLE_PARAMS_NUM_PATTERNS_OFFS); writel_relaxed(reg, device->base + TSPP2_INDEX_TABLE_PARAMS(table_id)); mutex_unlock(&device->mutex); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); dev_dbg(device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_indexing_patterns_clear); /* Pipe API functions */ /** * tspp2_pipe_memory_init() - Initialize pipe memory helper function. * * @pipe: The pipe to work on. * * The user is responsible for allocating the pipe's memory buffer via ION. * This helper function maps the given buffer to TSPP2 IOMMU memory space, * and sets the pipe's secure bit. * * Return 0 on success, error value otherwise. */ static int tspp2_pipe_memory_init(struct tspp2_pipe *pipe) { int ret = 0; u32 reg; size_t align; unsigned long dummy_size = 0; size_t len = 0; int domain = 0; int partition = 0; int hlos_group_attached = 0; int cpz_group_attached = 0; int vbif_clk_started = 0; if (pipe->cfg.is_secure) { domain = pipe->device->iommu_info.cpz_domain_num; partition = pipe->device->iommu_info.cpz_partition; align = SZ_1M; } else { domain = pipe->device->iommu_info.hlos_domain_num; partition = pipe->device->iommu_info.hlos_partition; align = SZ_4K; } if (tspp2_iommu_bypass) { ret = ion_phys(pipe->cfg.ion_client, pipe->cfg.buffer_handle, &pipe->iova, &len); dummy_size = 0; if (ret) { pr_err("%s: Failed to get buffer physical address, ret = %d\n", __func__, ret); return ret; } if ((pipe->device->num_secured_opened_pipes + pipe->device->num_non_secured_opened_pipes) == 0) { ret = tspp2_vbif_clock_start(pipe->device); if (ret) { pr_err( "%s: tspp2_vbif_clock_start failed, ret=%d\n", __func__, ret); return ret; } vbif_clk_started = 1; } } else { /* * We need to attach the group to enable the IOMMU and support * the required memory mapping. This needs to be done before * the first mapping is performed, so the number of opened pipes * (of each type: secure or non-secure) is used as a * reference count. Note that since the pipe descriptors are * always allocated from HLOS domain, the HLOS group must be * attached regardless of the pipe's security configuration. * The mutex is taken at this point so there is no problem with * synchronization. */ if ((pipe->device->num_secured_opened_pipes + pipe->device->num_non_secured_opened_pipes) == 0) { ret = tspp2_vbif_clock_start(pipe->device); if (ret) { pr_err("%s: tspp2_vbif_clock_start failed, ret=%d\n", __func__, ret); goto err_out; } vbif_clk_started = 1; pr_debug("%s: attaching HLOS group\n", __func__); ret = iommu_attach_group( pipe->device->iommu_info.hlos_domain, pipe->device->iommu_info.hlos_group); if (ret) { pr_err("%s: Failed attaching IOMMU HLOS group, %d\n", __func__, ret); goto err_out; } hlos_group_attached = 1; } if (pipe->cfg.is_secure && (pipe->device->num_secured_opened_pipes == 0)) { pr_debug("%s: attaching CPZ group\n", __func__); ret = iommu_attach_group( pipe->device->iommu_info.cpz_domain, pipe->device->iommu_info.cpz_group); if (ret) { pr_err("%s: Failed attaching IOMMU CPZ group, %d\n", __func__, ret); goto err_out; } cpz_group_attached = 1; } /* Map to TSPP2 IOMMU */ ret = ion_map_iommu(pipe->cfg.ion_client, pipe->cfg.buffer_handle, domain, partition, align, 0, &pipe->iova, &dummy_size, 0, 0); /* Uncached mapping */ if (ret) { pr_err("%s: Failed mapping buffer to TSPP2, %d\n", __func__, ret); goto err_out; } } if (pipe->cfg.is_secure) { reg = readl_relaxed(pipe->device->base + TSPP2_PIPE_SECURITY); reg |= (0x1 << pipe->hw_index); writel_relaxed(reg, pipe->device->base + TSPP2_PIPE_SECURITY); } dev_dbg(pipe->device->dev, "%s: successful\n", __func__); return 0; err_out: if (hlos_group_attached) { iommu_detach_group(pipe->device->iommu_info.hlos_domain, pipe->device->iommu_info.hlos_group); } if (cpz_group_attached) { iommu_detach_group(pipe->device->iommu_info.cpz_domain, pipe->device->iommu_info.cpz_group); } if (vbif_clk_started) tspp2_vbif_clock_stop(pipe->device); return ret; } /** * tspp2_pipe_memory_terminate() - Unmap pipe memory. * * @pipe: The pipe to work on. * * Unmap the pipe's memory and clear the pipe's secure bit. */ static void tspp2_pipe_memory_terminate(struct tspp2_pipe *pipe) { u32 reg; int domain = 0; int partition = 0; if (pipe->cfg.is_secure) { domain = pipe->device->iommu_info.cpz_domain_num; partition = pipe->device->iommu_info.cpz_partition; } else { domain = pipe->device->iommu_info.hlos_domain_num; partition = pipe->device->iommu_info.hlos_partition; } if (!tspp2_iommu_bypass) { ion_unmap_iommu(pipe->cfg.ion_client, pipe->cfg.buffer_handle, domain, partition); /* * Opposite to what is done in tspp2_pipe_memory_init(), * here we detach the IOMMU group when it is no longer in use. */ if (pipe->cfg.is_secure && (pipe->device->num_secured_opened_pipes == 0)) { pr_debug("%s: detaching CPZ group\n", __func__); iommu_detach_group( pipe->device->iommu_info.cpz_domain, pipe->device->iommu_info.cpz_group); } if ((pipe->device->num_secured_opened_pipes + pipe->device->num_non_secured_opened_pipes) == 0) { pr_debug("%s: detaching HLOS group\n", __func__); iommu_detach_group( pipe->device->iommu_info.hlos_domain, pipe->device->iommu_info.hlos_group); tspp2_vbif_clock_stop(pipe->device); } } else if ((pipe->device->num_secured_opened_pipes + pipe->device->num_non_secured_opened_pipes) == 0) { tspp2_vbif_clock_stop(pipe->device); } pipe->iova = 0; if (pipe->cfg.is_secure) { reg = readl_relaxed(pipe->device->base + TSPP2_PIPE_SECURITY); reg &= ~(0x1 << pipe->hw_index); writel_relaxed(reg, pipe->device->base + TSPP2_PIPE_SECURITY); } dev_dbg(pipe->device->dev, "%s: successful\n", __func__); } /** * tspp2_sps_pipe_init() - BAM SPS pipe configuration and initialization * * @pipe: The pipe to work on. * * Return 0 on success, error value otherwise. */ static int tspp2_sps_pipe_init(struct tspp2_pipe *pipe) { u32 descriptors_num; unsigned long dummy_size = 0; int ret = 0; int iommu_mapped = 0; if (pipe->cfg.buffer_size % pipe->cfg.sps_cfg.descriptor_size) { pr_err( "%s: Buffer size %d is not aligned to descriptor size %d\n", __func__, pipe->cfg.buffer_size, pipe->cfg.sps_cfg.descriptor_size); return -EINVAL; } pipe->sps_pipe = sps_alloc_endpoint(); if (!pipe->sps_pipe) { pr_err("%s: Failed to allocate BAM pipe\n", __func__); return -ENOMEM; } /* get default configuration */ sps_get_config(pipe->sps_pipe, &pipe->sps_connect_cfg); if (pipe->cfg.pipe_mode == TSPP2_SRC_PIPE_INPUT) { pipe->sps_connect_cfg.mode = SPS_MODE_DEST; pipe->sps_connect_cfg.source = SPS_DEV_HANDLE_MEM; pipe->sps_connect_cfg.destination = pipe->device->bam_handle; pipe->sps_connect_cfg.dest_pipe_index = pipe->hw_index; } else { pipe->sps_connect_cfg.mode = SPS_MODE_SRC; pipe->sps_connect_cfg.source = pipe->device->bam_handle; pipe->sps_connect_cfg.destination = SPS_DEV_HANDLE_MEM; pipe->sps_connect_cfg.src_pipe_index = pipe->hw_index; } pipe->sps_connect_cfg.desc.base = NULL; pipe->sps_connect_cfg.options = pipe->cfg.sps_cfg.setting; descriptors_num = (pipe->cfg.buffer_size / pipe->cfg.sps_cfg.descriptor_size); /* * If size of descriptors FIFO can hold N descriptors, we can submit * (N-1) descriptors only, therefore we allocate extra descriptor */ descriptors_num++; pipe->sps_connect_cfg.desc.size = (descriptors_num * sizeof(struct sps_iovec)); if (tspp2_iommu_bypass) { pipe->sps_connect_cfg.desc.base = dma_alloc_coherent(NULL, pipe->sps_connect_cfg.desc.size, &pipe->sps_connect_cfg.desc.phys_base, GFP_KERNEL); if (!pipe->sps_connect_cfg.desc.base) { pr_err("%s: Failed to allocate descriptor FIFO\n", __func__); ret = -ENOMEM; goto init_sps_failed_free_endpoint; } } else { pipe->desc_ion_handle = ion_alloc(pipe->cfg.ion_client, pipe->sps_connect_cfg.desc.size, SZ_4K, ION_HEAP(ION_IOMMU_HEAP_ID), 0); if (!pipe->desc_ion_handle) { pr_err("%s: Failed to allocate descriptors via ION\n", __func__); ret = -ENOMEM; goto init_sps_failed_free_endpoint; } ret = ion_map_iommu(pipe->cfg.ion_client, pipe->desc_ion_handle, pipe->device->iommu_info.hlos_domain_num, pipe->device->iommu_info.hlos_partition, SZ_4K, 0, &pipe->sps_connect_cfg.desc.phys_base, &dummy_size, 0, 0); /* Uncached mapping */ if (ret) { pr_err("%s: Failed mapping descriptors to IOMMU\n", __func__); goto init_sps_failed_free_mem; } iommu_mapped = 1; pipe->sps_connect_cfg.desc.base = ion_map_kernel(pipe->cfg.ion_client, pipe->desc_ion_handle); if (!pipe->sps_connect_cfg.desc.base) { pr_err("%s: Failed mapping descriptors to kernel\n", __func__); ret = -ENOMEM; goto init_sps_failed_free_mem; } } ret = sps_connect(pipe->sps_pipe, &pipe->sps_connect_cfg); if (ret) { pr_err("%s: Failed to connect BAM, %d\n", __func__, ret); goto init_sps_failed_free_mem; } pipe->sps_event.options = pipe->cfg.sps_cfg.wakeup_events; if (pipe->sps_event.options) { pipe->sps_event.mode = SPS_TRIGGER_CALLBACK; pipe->sps_event.callback = pipe->cfg.sps_cfg.callback; pipe->sps_event.xfer_done = NULL; pipe->sps_event.user = pipe->cfg.sps_cfg.user_info; ret = sps_register_event(pipe->sps_pipe, &pipe->sps_event); if (ret) { pr_err("%s: Failed to register pipe event, %d\n", __func__, ret); goto init_sps_failed_free_connection; } } dev_dbg(pipe->device->dev, "%s: successful\n", __func__); return 0; init_sps_failed_free_connection: sps_disconnect(pipe->sps_pipe); init_sps_failed_free_mem: if (tspp2_iommu_bypass) { dma_free_coherent(NULL, pipe->sps_connect_cfg.desc.size, pipe->sps_connect_cfg.desc.base, pipe->sps_connect_cfg.desc.phys_base); } else { if (pipe->sps_connect_cfg.desc.base) ion_unmap_kernel(pipe->cfg.ion_client, pipe->desc_ion_handle); if (iommu_mapped) { ion_unmap_iommu(pipe->cfg.ion_client, pipe->desc_ion_handle, pipe->device->iommu_info.hlos_domain_num, pipe->device->iommu_info.hlos_partition); } ion_free(pipe->cfg.ion_client, pipe->desc_ion_handle); } init_sps_failed_free_endpoint: sps_free_endpoint(pipe->sps_pipe); return ret; } /** * tspp2_sps_queue_descriptors() - Queue BAM SPS descriptors * * @pipe: The pipe to work on. * * Return 0 on success, error value otherwise. */ static int tspp2_sps_queue_descriptors(struct tspp2_pipe *pipe) { int ret = 0; u32 data_offset = 0; u32 desc_length = pipe->cfg.sps_cfg.descriptor_size; u32 desc_flags = pipe->cfg.sps_cfg.descriptor_flags; u32 data_length = pipe->cfg.buffer_size; while (data_length > 0) { ret = sps_transfer_one(pipe->sps_pipe, pipe->iova + data_offset, desc_length, pipe->cfg.sps_cfg.user_info, desc_flags); if (ret) { pr_err("%s: sps_transfer_one failed, %d\n", __func__, ret); return ret; } data_offset += desc_length; data_length -= desc_length; } return 0; } /** * tspp2_sps_pipe_terminate() - Disconnect and terminate SPS BAM pipe * * @pipe: The pipe to work on. * * Return 0 on success, error value otherwise. */ static int tspp2_sps_pipe_terminate(struct tspp2_pipe *pipe) { int ret; ret = sps_disconnect(pipe->sps_pipe); if (ret) { pr_err("%s: failed to disconnect BAM pipe, %d\n", __func__, ret); return ret; } if (tspp2_iommu_bypass) { dma_free_coherent(NULL, pipe->sps_connect_cfg.desc.size, pipe->sps_connect_cfg.desc.base, pipe->sps_connect_cfg.desc.phys_base); } else { ion_unmap_kernel(pipe->cfg.ion_client, pipe->desc_ion_handle); ion_unmap_iommu(pipe->cfg.ion_client, pipe->desc_ion_handle, pipe->device->iommu_info.hlos_domain_num, pipe->device->iommu_info.hlos_partition); ion_free(pipe->cfg.ion_client, pipe->desc_ion_handle); } pipe->sps_connect_cfg.desc.base = NULL; ret = sps_free_endpoint(pipe->sps_pipe); if (ret) { pr_err("%s: failed to release BAM end-point, %d\n", __func__, ret); return ret; } return 0; } /** * tspp2_pipe_open() - Open a pipe for use. * * @dev_id: TSPP2 device ID. * @cfg: Pipe configuration parameters. * @iova: TSPP2 IOMMU virtual address of the pipe's buffer. * @pipe_handle: Opened pipe handle. * * Return 0 on success, error value otherwise. */ int tspp2_pipe_open(u32 dev_id, const struct tspp2_pipe_config_params *cfg, ion_phys_addr_t *iova, u32 *pipe_handle) { struct tspp2_device *device; struct tspp2_pipe *pipe; int i; int ret = 0; if (dev_id >= TSPP2_NUM_DEVICES) { pr_err("%s: Invalid device ID %d\n", __func__, dev_id); return -ENODEV; } if (!cfg || !iova || !pipe_handle) { pr_err("%s: Invalid parameters\n", __func__); return -EINVAL; } /* Some minimal sanity tests on the pipe configuration: */ if (!cfg->ion_client || !cfg->buffer_handle) { pr_err("%s: Invalid parameters\n", __func__); return -EINVAL; } device = tspp2_devices[dev_id]; if (!device) { pr_err("%s: Invalid device\n", __func__); return -ENODEV; } ret = pm_runtime_get_sync(device->dev); if (ret < 0) return ret; if (mutex_lock_interruptible(&device->mutex)) { pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); return -ERESTARTSYS; } if (!device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&device->mutex); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); return -EPERM; } /* Find a free pipe */ for (i = 0; i < TSPP2_NUM_PIPES; i++) { pipe = &device->pipes[i]; if (!pipe->opened) break; } if (i == TSPP2_NUM_PIPES) { pr_err("%s: No available pipes\n", __func__); mutex_unlock(&device->mutex); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); return -ENOMEM; } pipe->hw_index = i; /* Actual pipe threshold is set when the pipe is attached to a source */ pipe->threshold = 0; pipe->cfg = *cfg; pipe->ref_cnt = 0; /* device back-pointer is already initialized, always remains valid */ ret = tspp2_pipe_memory_init(pipe); if (ret) { pr_err("%s: Error initializing pipe memory\n", __func__); mutex_unlock(&device->mutex); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); return ret; } ret = tspp2_sps_pipe_init(pipe); if (ret) { pr_err("%s: Error initializing BAM pipe\n", __func__); tspp2_pipe_memory_terminate(pipe); mutex_unlock(&device->mutex); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); return ret; } /* For output pipes, we queue BAM descriptors here so they are ready */ if (pipe->cfg.pipe_mode == TSPP2_SRC_PIPE_OUTPUT) { ret = tspp2_sps_queue_descriptors(pipe); if (ret) { pr_err("%s: Error queuing BAM pipe descriptors\n", __func__); tspp2_sps_pipe_terminate(pipe); tspp2_pipe_memory_terminate(pipe); mutex_unlock(&device->mutex); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); return ret; } } /* Reset counter */ writel_relaxed((0x1 << pipe->hw_index), device->base + TSPP2_DATA_NOT_SENT_ON_PIPE_RESET); /* Return handle to the caller */ *pipe_handle = (u32)pipe; *iova = pipe->iova; pipe->opened = 1; if (pipe->cfg.is_secure) device->num_secured_opened_pipes++; else device->num_non_secured_opened_pipes++; mutex_unlock(&device->mutex); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); dev_dbg(device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_pipe_open); /** * tspp2_pipe_close() - Close an opened pipe. * * @pipe_handle: Pipe to be closed. * * Return 0 on success, error value otherwise. */ int tspp2_pipe_close(u32 pipe_handle) { int ret; struct tspp2_pipe *pipe = (struct tspp2_pipe *)pipe_handle; if (!pipe) { pr_err("%s: Invalid pipe handle\n", __func__); return -EINVAL; } ret = pm_runtime_get_sync(pipe->device->dev); if (ret < 0) return ret; mutex_lock(&pipe->device->mutex); if (!pipe->device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&pipe->device->mutex); pm_runtime_mark_last_busy(pipe->device->dev); pm_runtime_put_autosuspend(pipe->device->dev); return -EPERM; } if (!pipe->opened) { pr_err("%s: Pipe already closed\n", __func__); mutex_unlock(&pipe->device->mutex); pm_runtime_mark_last_busy(pipe->device->dev); pm_runtime_put_autosuspend(pipe->device->dev); return -EINVAL; } if (pipe->ref_cnt > 0) { pr_err("%s: Pipe %u is still attached to a source\n", __func__, pipe_handle); mutex_unlock(&pipe->device->mutex); pm_runtime_mark_last_busy(pipe->device->dev); pm_runtime_put_autosuspend(pipe->device->dev); return -EPERM; } /* * Note: need to decrement the pipe reference count here, before * calling tspp2_pipe_memory_terminate(). */ if (pipe->cfg.is_secure) pipe->device->num_secured_opened_pipes--; else pipe->device->num_non_secured_opened_pipes--; tspp2_sps_pipe_terminate(pipe); tspp2_pipe_memory_terminate(pipe); pipe->iova = 0; pipe->opened = 0; mutex_unlock(&pipe->device->mutex); pm_runtime_mark_last_busy(pipe->device->dev); pm_runtime_put_autosuspend(pipe->device->dev); dev_dbg(pipe->device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_pipe_close); /* Source API functions */ /** * tspp2_src_open() - Open a new source for use. * * @dev_id: TSPP2 device ID. * @cfg: Source configuration parameters. * @src_handle: Opened source handle. * * Return 0 on success, error value otherwise. */ int tspp2_src_open(u32 dev_id, struct tspp2_src_cfg *cfg, u32 *src_handle) { int ret; int i; struct tspp2_device *device; struct tspp2_src *src; enum tspp2_src_input input; if (dev_id >= TSPP2_NUM_DEVICES) { pr_err("%s: Invalid device ID %d\n", __func__, dev_id); return -ENODEV; } if (!src_handle) { pr_err("%s: Invalid source handle pointer\n", __func__); return -EINVAL; } if (!cfg) { pr_err("%s: Invalid configuration parameters\n", __func__); return -EINVAL; } device = tspp2_devices[dev_id]; if (!device) { pr_err("%s: Invalid device\n", __func__); return -ENODEV; } ret = pm_runtime_get_sync(device->dev); if (ret < 0) return ret; if (mutex_lock_interruptible(&device->mutex)) { pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); return -ERESTARTSYS; } if (!device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&device->mutex); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); return -EPERM; } input = cfg->input; if ((input == TSPP2_INPUT_TSIF0) || (input == TSPP2_INPUT_TSIF1)) { /* Input from TSIF */ if (device->tsif_sources[input].opened) { pr_err("%s: TSIF input %d already opened\n", __func__, input); mutex_unlock(&device->mutex); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); return -EINVAL; } src = &device->tsif_sources[input]; /* * When writing to HW registers that are relevant to sources * of both TSIF and memory input types, the register offsets * for the TSIF-related registers come after the memory-related * registers. For example: for TSPP2_SRC_CONFIG(n), n=[0..9], * indexes 0..7 are for memory inputs, and indexes 8, 9 are * for TSIF inputs. */ src->hw_index = TSPP2_NUM_MEM_INPUTS + input; /* Save TSIF source parameters in TSIF device */ device->tsif_devices[input].mode = cfg->params.tsif_params.tsif_mode; device->tsif_devices[input].clock_inverse = cfg->params.tsif_params.clock_inverse; device->tsif_devices[input].data_inverse = cfg->params.tsif_params.data_inverse; device->tsif_devices[input].sync_inverse = cfg->params.tsif_params.sync_inverse; device->tsif_devices[input].enable_inverse = cfg->params.tsif_params.enable_inverse; } else { /* Input from memory */ for (i = 0; i < TSPP2_NUM_MEM_INPUTS; i++) { if (!device->mem_sources[i].opened) break; } if (i == TSPP2_NUM_MEM_INPUTS) { pr_err("%s: No memory inputs available\n", __func__); mutex_unlock(&device->mutex); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); return -ENOMEM; } src = &device->mem_sources[i]; src->hw_index = i; } src->opened = 1; src->input = input; src->pkt_format = TSPP2_PACKET_FORMAT_188_RAW; /* default value */ src->scrambling_bits_monitoring = TSPP2_SRC_SCRAMBLING_MONITOR_NONE; INIT_LIST_HEAD(&src->batches_list); INIT_LIST_HEAD(&src->filters_list); src->input_pipe = NULL; INIT_LIST_HEAD(&src->output_pipe_list); src->num_associated_batches = 0; src->num_associated_pipes = 0; src->num_associated_filters = 0; src->reserved_filter_hw_index = 0; src->event_callback = NULL; src->event_cookie = NULL; src->event_bitmask = 0; src->enabled = 0; /* device back-pointer is already initialized, always remains valid */ /* Reset source-related registers */ if ((input == TSPP2_INPUT_TSIF0) || (input == TSPP2_INPUT_TSIF1)) { writel_relaxed((0x1 << TSIF_INPUT_SRC_CONFIG_16_BATCHES_OFFS), device->base + TSPP2_TSIF_INPUT_SRC_CONFIG(src->input)); } else { /* * Disable memory inputs. Set mode of operation to 16 batches. * Configure last batch to be associated with this source. */ writel_relaxed(TSPP2_DEFAULT_MEM_SRC_CONFIG, device->base + TSPP2_MEM_INPUT_SRC_CONFIG(src->hw_index)); } writel_relaxed(0, device->base + TSPP2_SRC_DEST_PIPES(src->hw_index)); writel_relaxed(TSPP2_DEFAULT_SRC_CONFIG, device->base + TSPP2_SRC_CONFIG(src->hw_index)); writel_relaxed((0x1 << src->hw_index), device->base + TSPP2_SRC_TOTAL_TSP_RESET); writel_relaxed((0x1 << src->hw_index), device->base + TSPP2_SRC_FILTERED_OUT_TSP_RESET); /* Return handle to the caller */ *src_handle = (u32)src; mutex_unlock(&device->mutex); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); dev_dbg(device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_src_open); /** * tspp2_src_close() - Close an opened source. * * @src_handle: Source to be closed. * * Return 0 on success, error value otherwise. */ int tspp2_src_close(u32 src_handle) { unsigned long flags; struct tspp2_src *src = (struct tspp2_src *)src_handle; if (!src) { pr_err("%s: Invalid source handle\n", __func__); return -EINVAL; } mutex_lock(&src->device->mutex); if (!src->device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&src->device->mutex); return -EPERM; } if (!src->opened) { pr_err("%s: Source already closed\n", __func__); mutex_unlock(&src->device->mutex); return -EINVAL; } if (src->enabled) { pr_err("%s: Source needs to be disabled before it can be closed\n", __func__); mutex_unlock(&src->device->mutex); return -EPERM; } /* Verify resources have been released by the caller */ if ((src->num_associated_batches > 0) || (src->num_associated_pipes > 0) || (src->num_associated_filters > 0)) { pr_err("%s: Source's resources need to be removed before it can be closed\n", __func__); mutex_unlock(&src->device->mutex); return -EPERM; } /* * Most fields are reset to default values when opening a source, so * there is no need to reset them all here. We only need to mark the * source as closed. */ src->opened = 0; spin_lock_irqsave(&src->device->spinlock, flags); src->event_callback = NULL; src->event_cookie = NULL; src->event_bitmask = 0; spin_unlock_irqrestore(&src->device->spinlock, flags); src->enabled = 0; /* * Source-related HW registers are reset when opening a source, so * we don't reser them here. Note that a source is disabled before * it is closed, so no need to disable it here either. */ mutex_unlock(&src->device->mutex); dev_dbg(src->device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_src_close); /** * tspp2_src_parsing_option_set() - Set source parsing configuration option. * * @src_handle: Source to configure. * @option: Parsing configuration option to enable / disable. * @enable: Enable / disable option. * * Return 0 on success, error value otherwise. */ int tspp2_src_parsing_option_set(u32 src_handle, enum tspp2_src_parsing_option option, int enable) { int ret; u32 reg = 0; struct tspp2_src *src = (struct tspp2_src *)src_handle; if (!src) { pr_err("%s: Invalid source handle\n", __func__); return -EINVAL; } ret = pm_runtime_get_sync(src->device->dev); if (ret < 0) return ret; if (mutex_lock_interruptible(&src->device->mutex)) { pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -ERESTARTSYS; } if (!src->device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EPERM; } if (!src->opened) { pr_err("%s: Source not opened\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EINVAL; } reg = readl_relaxed(src->device->base + TSPP2_SRC_CONFIG(src->hw_index)); switch (option) { case TSPP2_SRC_PARSING_OPT_CHECK_CONTINUITY: if (enable) reg |= (0x1 << SRC_CONFIG_CHECK_CONT_OFFS); else reg &= ~(0x1 << SRC_CONFIG_CHECK_CONT_OFFS); break; case TSPP2_SRC_PARSING_OPT_IGNORE_DISCONTINUITY: if (enable) reg |= (0x1 << SRC_CONFIG_IGNORE_DISCONT_OFFS); else reg &= ~(0x1 << SRC_CONFIG_IGNORE_DISCONT_OFFS); break; case TSPP2_SRC_PARSING_OPT_ASSUME_DUPLICATE_PACKETS: if (enable) reg |= (0x1 << SRC_CONFIG_ASSUME_DUPLICATES_OFFS); else reg &= ~(0x1 << SRC_CONFIG_ASSUME_DUPLICATES_OFFS); break; case TSPP2_SRC_PARSING_OPT_DISCARD_INVALID_AF_PACKETS: if (enable) reg |= (0x1 << SRC_CONFIG_DISCARD_INVALID_AF_OFFS); else reg &= ~(0x1 << SRC_CONFIG_DISCARD_INVALID_AF_OFFS); break; case TSPP2_SRC_PARSING_OPT_VERIFY_PES_START: if (enable) reg |= (0x1 << SRC_CONFIG_VERIFY_PES_START_OFFS); else reg &= ~(0x1 << SRC_CONFIG_VERIFY_PES_START_OFFS); break; default: pr_err("%s: Invalid option %d\n", __func__, option); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EINVAL; } writel_relaxed(reg, src->device->base + TSPP2_SRC_CONFIG(src->hw_index)); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); dev_dbg(src->device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_src_parsing_option_set); /** * tspp2_src_parsing_option_get() - Get source parsing configuration option. * * @src_handle: Source handle. * @option: Parsing configuration option to get. * @enable: Option's enable / disable indication. * * Return 0 on success, error value otherwise. */ int tspp2_src_parsing_option_get(u32 src_handle, enum tspp2_src_parsing_option option, int *enable) { int ret; u32 reg = 0; struct tspp2_src *src = (struct tspp2_src *)src_handle; if (!src) { pr_err("%s: Invalid source handle\n", __func__); return -EINVAL; } if (!enable) { pr_err("%s: NULL pointer\n", __func__); return -EINVAL; } ret = pm_runtime_get_sync(src->device->dev); if (ret < 0) return ret; if (mutex_lock_interruptible(&src->device->mutex)) { pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -ERESTARTSYS; } if (!src->device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EPERM; } if (!src->opened) { pr_err("%s: Source not opened\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EINVAL; } reg = readl_relaxed(src->device->base + TSPP2_SRC_CONFIG(src->hw_index)); switch (option) { case TSPP2_SRC_PARSING_OPT_CHECK_CONTINUITY: *enable = ((reg >> SRC_CONFIG_CHECK_CONT_OFFS) & 0x1); break; case TSPP2_SRC_PARSING_OPT_IGNORE_DISCONTINUITY: *enable = ((reg >> SRC_CONFIG_IGNORE_DISCONT_OFFS) & 0x1); break; case TSPP2_SRC_PARSING_OPT_ASSUME_DUPLICATE_PACKETS: *enable = ((reg >> SRC_CONFIG_ASSUME_DUPLICATES_OFFS) & 0x1); break; case TSPP2_SRC_PARSING_OPT_DISCARD_INVALID_AF_PACKETS: *enable = ((reg >> SRC_CONFIG_DISCARD_INVALID_AF_OFFS) & 0x1); break; case TSPP2_SRC_PARSING_OPT_VERIFY_PES_START: *enable = ((reg >> SRC_CONFIG_VERIFY_PES_START_OFFS) & 0x1); break; default: pr_err("%s: Invalid option %d\n", __func__, option); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EINVAL; } mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); dev_dbg(src->device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_src_parsing_option_get); /** * tspp2_src_sync_byte_config_set() - Set source sync byte configuration. * * @src_handle: Source to configure. * @check_sync_byte: Check TS packet sync byte. * @sync_byte_value: Sync byte value to check (e.g., 0x47). * * Return 0 on success, error value otherwise. */ int tspp2_src_sync_byte_config_set(u32 src_handle, int check_sync_byte, u8 sync_byte_value) { int ret; u32 reg = 0; struct tspp2_src *src = (struct tspp2_src *)src_handle; if (!src) { pr_err("%s: Invalid source handle\n", __func__); return -EINVAL; } ret = pm_runtime_get_sync(src->device->dev); if (ret < 0) return ret; if (mutex_lock_interruptible(&src->device->mutex)) { pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -ERESTARTSYS; } if (!src->device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EPERM; } if (!src->opened) { pr_err("%s: Source not opened\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EINVAL; } reg = readl_relaxed(src->device->base + TSPP2_SRC_CONFIG(src->hw_index)); if (check_sync_byte) reg |= (0x1 << SRC_CONFIG_CHECK_SYNC_OFFS); else reg &= ~(0x1 << SRC_CONFIG_CHECK_SYNC_OFFS); reg &= ~(0xFF << SRC_CONFIG_SYNC_BYTE_OFFS); reg |= (sync_byte_value << SRC_CONFIG_SYNC_BYTE_OFFS); writel_relaxed(reg, src->device->base + TSPP2_SRC_CONFIG(src->hw_index)); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); dev_dbg(src->device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_src_sync_byte_config_set); /** * tspp2_src_sync_byte_config_get() - Get source sync byte configuration. * * @src_handle: Source handle. * @check_sync_byte: Check TS packet sync byte indication. * @sync_byte_value: Sync byte value. * * Return 0 on success, error value otherwise. */ int tspp2_src_sync_byte_config_get(u32 src_handle, int *check_sync_byte, u8 *sync_byte_value) { int ret; u32 reg = 0; struct tspp2_src *src = (struct tspp2_src *)src_handle; if (!src) { pr_err("%s: Invalid source handle\n", __func__); return -EINVAL; } if (!check_sync_byte || !sync_byte_value) { pr_err("%s: NULL pointer\n", __func__); return -EINVAL; } ret = pm_runtime_get_sync(src->device->dev); if (ret < 0) return ret; if (mutex_lock_interruptible(&src->device->mutex)) { pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -ERESTARTSYS; } if (!src->device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EPERM; } if (!src->opened) { pr_err("%s: Source not opened\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EINVAL; } reg = readl_relaxed(src->device->base + TSPP2_SRC_CONFIG(src->hw_index)); *check_sync_byte = (reg >> SRC_CONFIG_CHECK_SYNC_OFFS) & 0x1; *sync_byte_value = (reg >> SRC_CONFIG_SYNC_BYTE_OFFS) & 0xFF; mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); dev_dbg(src->device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_src_sync_byte_config_get); /** * tspp2_src_scrambling_config_set() - Set source scrambling configuration. * * @src_handle: Source to configure. * @cfg: Scrambling configuration to set. * * Return 0 on success, error value otherwise. */ int tspp2_src_scrambling_config_set(u32 src_handle, const struct tspp2_src_scrambling_config *cfg) { int ret; u32 reg = 0; struct tspp2_src *src = (struct tspp2_src *)src_handle; if (!src) { pr_err("%s: Invalid source handle\n", __func__); return -EINVAL; } if (!cfg) { pr_err("%s: NULL pointer\n", __func__); return -EINVAL; } ret = pm_runtime_get_sync(src->device->dev); if (ret < 0) return ret; if (mutex_lock_interruptible(&src->device->mutex)) { pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -ERESTARTSYS; } if (!src->device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EPERM; } if (!src->opened) { pr_err("%s: Source not opened\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EINVAL; } reg = readl_relaxed(src->device->base + TSPP2_SRC_CONFIG(src->hw_index)); /* Clear all scrambling configuration bits before setting them */ reg &= ~(0x3 << SRC_CONFIG_SCRAMBLING0_OFFS); reg &= ~(0x3 << SRC_CONFIG_SCRAMBLING1_OFFS); reg &= ~(0x3 << SRC_CONFIG_SCRAMBLING2_OFFS); reg &= ~(0x3 << SRC_CONFIG_SCRAMBLING3_OFFS); reg &= ~(0x3 << SRC_CONFIG_SCRAMBLING_MONITOR_OFFS); reg |= (cfg->scrambling_0_ctrl << SRC_CONFIG_SCRAMBLING0_OFFS); reg |= (cfg->scrambling_1_ctrl << SRC_CONFIG_SCRAMBLING1_OFFS); reg |= (cfg->scrambling_2_ctrl << SRC_CONFIG_SCRAMBLING2_OFFS); reg |= (cfg->scrambling_3_ctrl << SRC_CONFIG_SCRAMBLING3_OFFS); reg |= (cfg->scrambling_bits_monitoring << SRC_CONFIG_SCRAMBLING_MONITOR_OFFS); writel_relaxed(reg, src->device->base + TSPP2_SRC_CONFIG(src->hw_index)); src->scrambling_bits_monitoring = cfg->scrambling_bits_monitoring; mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); dev_dbg(src->device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_src_scrambling_config_set); /** * tspp2_src_scrambling_config_get() - Get source scrambling configuration. * * @src_handle: Source handle. * @cfg: Scrambling configuration. * * Return 0 on success, error value otherwise. */ int tspp2_src_scrambling_config_get(u32 src_handle, struct tspp2_src_scrambling_config *cfg) { int ret; u32 reg = 0; struct tspp2_src *src = (struct tspp2_src *)src_handle; if (!src) { pr_err("%s: Invalid source handle\n", __func__); return -EINVAL; } if (!cfg) { pr_err("%s: NULL pointer\n", __func__); return -EINVAL; } ret = pm_runtime_get_sync(src->device->dev); if (ret < 0) return ret; if (mutex_lock_interruptible(&src->device->mutex)) { pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -ERESTARTSYS; } if (!src->device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EPERM; } if (!src->opened) { pr_err("%s: Source not opened\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EINVAL; } reg = readl_relaxed(src->device->base + TSPP2_SRC_CONFIG(src->hw_index)); cfg->scrambling_0_ctrl = ((reg >> SRC_CONFIG_SCRAMBLING0_OFFS) & 0x3); cfg->scrambling_1_ctrl = ((reg >> SRC_CONFIG_SCRAMBLING1_OFFS) & 0x3); cfg->scrambling_2_ctrl = ((reg >> SRC_CONFIG_SCRAMBLING2_OFFS) & 0x3); cfg->scrambling_3_ctrl = ((reg >> SRC_CONFIG_SCRAMBLING3_OFFS) & 0x3); cfg->scrambling_bits_monitoring = ((reg >> SRC_CONFIG_SCRAMBLING_MONITOR_OFFS) & 0x3); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); dev_dbg(src->device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_src_scrambling_config_get); /** * tspp2_src_packet_format_set() - Set source packet size and format. * * @src_handle: Source to configure. * @format: Packet format. * * Return 0 on success, error value otherwise. */ int tspp2_src_packet_format_set(u32 src_handle, enum tspp2_packet_format format) { int ret; u32 reg = 0; struct tspp2_src *src = (struct tspp2_src *)src_handle; if (!src) { pr_err("%s: Invalid source handle\n", __func__); return -EINVAL; } ret = pm_runtime_get_sync(src->device->dev); if (ret < 0) return ret; if (mutex_lock_interruptible(&src->device->mutex)) { pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -ERESTARTSYS; } if (!src->device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EPERM; } if (!src->opened) { pr_err("%s: Source not opened\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EINVAL; } if (src->input == TSPP2_INPUT_MEMORY) { reg = readl_relaxed(src->device->base + TSPP2_MEM_INPUT_SRC_CONFIG(src->hw_index)); reg &= ~(0x1 << MEM_INPUT_SRC_CONFIG_STAMP_SUFFIX_OFFS); reg &= ~(0x1 << MEM_INPUT_SRC_CONFIG_STAMP_EN_OFFS); switch (format) { case TSPP2_PACKET_FORMAT_188_RAW: /* We do not need to set any bit */ break; case TSPP2_PACKET_FORMAT_192_HEAD: reg |= (0x1 << MEM_INPUT_SRC_CONFIG_STAMP_EN_OFFS); break; case TSPP2_PACKET_FORMAT_192_TAIL: reg |= (0x1 << MEM_INPUT_SRC_CONFIG_STAMP_EN_OFFS); reg |= (0x1 << MEM_INPUT_SRC_CONFIG_STAMP_SUFFIX_OFFS); break; default: pr_err("%s: Unknown packet format\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EINVAL; } writel_relaxed(reg, src->device->base + TSPP2_MEM_INPUT_SRC_CONFIG(src->hw_index)); } src->pkt_format = format; /* Update source's input pipe threshold if needed */ if (src->input_pipe) { if (src->pkt_format == TSPP2_PACKET_FORMAT_188_RAW) src->input_pipe->threshold = 188; else src->input_pipe->threshold = 192; writel_relaxed(src->input_pipe->threshold, src->input_pipe->device->base + TSPP2_PIPE_THRESH_CONFIG(src->input_pipe->hw_index)); } mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); dev_dbg(src->device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_src_packet_format_set); /** * tspp2_src_pipe_attach() - Attach a pipe to a source. * * @src_handle: Source to attach the pipe to. * @pipe_handle: Pipe to attach to the source. * @cfg: For output pipes - the pipe's pull mode parameters. * It is not allowed to pass NULL for output pipes. * For input pipes this is irrelevant and the caller can * pass NULL. * * This function attaches a given pipe to a given source. * The pipe's mode (input or output) was set when the pipe was opened. * An input pipe can be attached to a single source (with memory input). * A source can have multiple output pipes attached, and an output pipe can * be attached to multiple sources. * * Return 0 on success, error value otherwise. */ int tspp2_src_pipe_attach(u32 src_handle, u32 pipe_handle, const struct tspp2_pipe_pull_mode_params *cfg) { int ret; struct tspp2_src *src = (struct tspp2_src *)src_handle; struct tspp2_pipe *pipe = (struct tspp2_pipe *)pipe_handle; struct tspp2_output_pipe *output_pipe = NULL; u32 reg; if (!src || !pipe) { pr_err("%s: Invalid source or pipe handle\n", __func__); return -EINVAL; } ret = pm_runtime_get_sync(src->device->dev); if (ret < 0) return ret; if (mutex_lock_interruptible(&src->device->mutex)) { pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -ERESTARTSYS; } if (!src->device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EPERM; } if (!src->opened) { pr_err("%s: Source not opened\n", __func__); goto err_inval; } if (!pipe->opened) { pr_err("%s: Pipe not opened\n", __func__); goto err_inval; } if ((pipe->cfg.pipe_mode == TSPP2_SRC_PIPE_OUTPUT) && (cfg == NULL)) { pr_err("%s: Invalid pull mode parameters\n", __func__); goto err_inval; } if (pipe->cfg.pipe_mode == TSPP2_SRC_PIPE_INPUT) { if (src->input_pipe != NULL) { pr_err("%s: Source already has an input pipe attached\n", __func__); goto err_inval; } if (pipe->ref_cnt > 0) { pr_err( "%s: Pipe %u is already attached to a source. An input pipe can only be attached once\n", __func__, pipe_handle); goto err_inval; } /* * Input pipe threshold is determined according to the * source's packet size. */ if (src->pkt_format == TSPP2_PACKET_FORMAT_188_RAW) pipe->threshold = 188; else pipe->threshold = 192; src->input_pipe = pipe; reg = readl_relaxed(src->device->base + TSPP2_MEM_INPUT_SRC_CONFIG(src->hw_index)); reg &= ~(0x1F << MEM_INPUT_SRC_CONFIG_INPUT_PIPE_OFFS); reg |= (pipe->hw_index << MEM_INPUT_SRC_CONFIG_INPUT_PIPE_OFFS); writel_relaxed(reg, src->device->base + TSPP2_MEM_INPUT_SRC_CONFIG(src->hw_index)); } else { list_for_each_entry(output_pipe, &src->output_pipe_list, link) { if (output_pipe->pipe == pipe) { pr_err( "%s: Output pipe %u is already attached to source %u\n", __func__, pipe_handle, src_handle); goto err_inval; } } output_pipe = kmalloc(sizeof(struct tspp2_output_pipe), GFP_KERNEL); if (!output_pipe) { pr_err("%s: No memory to save output pipe\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -ENOMEM; } output_pipe->pipe = pipe; pipe->threshold = (cfg->threshold & 0xFFFF); list_add_tail(&output_pipe->link, &src->output_pipe_list); reg = readl_relaxed(src->device->base + TSPP2_SRC_DEST_PIPES(src->hw_index)); if (cfg->is_stalling) reg |= (0x1 << pipe->hw_index); else reg &= ~(0x1 << pipe->hw_index); writel_relaxed(reg, src->device->base + TSPP2_SRC_DEST_PIPES(src->hw_index)); } reg = readl_relaxed(pipe->device->base + TSPP2_PIPE_THRESH_CONFIG(pipe->hw_index)); if ((pipe->cfg.pipe_mode == TSPP2_SRC_PIPE_OUTPUT) && (pipe->ref_cnt > 0) && (pipe->threshold != (reg & 0xFFFF))) { pr_warn("%s: overwriting output pipe threshold\n", __func__); } writel_relaxed(pipe->threshold, pipe->device->base + TSPP2_PIPE_THRESH_CONFIG(pipe->hw_index)); pipe->ref_cnt++; src->num_associated_pipes++; mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); dev_dbg(src->device->dev, "%s: successful\n", __func__); return 0; err_inval: mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EINVAL; } EXPORT_SYMBOL(tspp2_src_pipe_attach); /** * tspp2_src_pipe_detach() - Detach a pipe from a source. * * @src_handle: Source to detach the pipe from. * @pipe_handle: Pipe to detach from the source. * * Detaches a pipe from a source. The given pipe should have been previously * attached to this source as either an input pipe or an output pipe. * Note: there is no checking if this pipe is currently defined as the output * pipe of any operation! * * Return 0 on success, error value otherwise. */ int tspp2_src_pipe_detach(u32 src_handle, u32 pipe_handle) { int ret; struct tspp2_src *src = (struct tspp2_src *)src_handle; struct tspp2_pipe *pipe = (struct tspp2_pipe *)pipe_handle; struct tspp2_output_pipe *output_pipe = NULL; int found = 0; u32 reg; if (!src || !pipe) { pr_err("%s: Invalid source or pipe handle\n", __func__); return -EINVAL; } ret = pm_runtime_get_sync(src->device->dev); if (ret < 0) return ret; mutex_lock(&src->device->mutex); if (!src->device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EPERM; } if (!src->opened) { pr_err("%s: Source not opened\n", __func__); goto err_inval; } if (!pipe->opened) { pr_err("%s: Pipe not opened\n", __func__); goto err_inval; } if (pipe->cfg.pipe_mode == TSPP2_SRC_PIPE_INPUT) { if (src->input_pipe != pipe) { pr_err( "%s: Input pipe %u is not attached to source %u\n", __func__, pipe_handle, src_handle); goto err_inval; } writel_relaxed(0xFFFF, src->input_pipe->device->base + TSPP2_PIPE_THRESH_CONFIG(src->input_pipe->hw_index)); if (src->enabled) { pr_warn("%s: Detaching input pipe from an active memory source\n", __func__); } /* * Note: not updating TSPP2_MEM_INPUT_SRC_CONFIG to reflect * this pipe is detached, since there is no invalid value we * can write instead. tspp2_src_pipe_attach() already takes * care of zeroing the relevant bit-field before writing the * new pipe nummber. */ src->input_pipe = NULL; } else { list_for_each_entry(output_pipe, &src->output_pipe_list, link) { if (output_pipe->pipe == pipe) { found = 1; break; } } if (found) { list_del(&output_pipe->link); kfree(output_pipe); reg = readl_relaxed(src->device->base + TSPP2_SRC_DEST_PIPES(src->hw_index)); reg &= ~(0x1 << pipe->hw_index); writel_relaxed(reg, src->device->base + TSPP2_SRC_DEST_PIPES(src->hw_index)); if (pipe->ref_cnt == 1) { writel_relaxed(0xFFFF, pipe->device->base + TSPP2_PIPE_THRESH_CONFIG( pipe->hw_index)); } } else { pr_err("%s: Output pipe %u is not attached to source %u\n", __func__, pipe_handle, src_handle); goto err_inval; } } pipe->ref_cnt--; src->num_associated_pipes--; mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); dev_dbg(src->device->dev, "%s: successful\n", __func__); return 0; err_inval: mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EINVAL; } EXPORT_SYMBOL(tspp2_src_pipe_detach); /** * tspp2_src_enable() - Enable source. * * @src_handle: Source to enable. * * Return 0 on success, error value otherwise. */ int tspp2_src_enable(u32 src_handle) { struct tspp2_src *src = (struct tspp2_src *)src_handle; u32 reg; int ret; if (!src) { pr_err("%s: Invalid source handle\n", __func__); return -EINVAL; } ret = pm_runtime_get_sync(src->device->dev); if (ret < 0) return ret; if (mutex_lock_interruptible(&src->device->mutex)) { pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -ERESTARTSYS; } if (!src->device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EPERM; } if (!src->opened) { pr_err("%s: Source not opened\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EINVAL; } if (src->enabled) { pr_warn("%s: Source already enabled\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return 0; } /* * Memory sources require their input pipe to be configured * before enabling the source. */ if ((src->input == TSPP2_INPUT_MEMORY) && (src->input_pipe == NULL)) { pr_err("%s: A memory source must have an input pipe attached before enabling the source", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EINVAL; } if (src->device->num_enabled_sources == 0) { ret = tspp2_clock_start(src->device); if (ret) { mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return ret; } __pm_stay_awake(&src->device->wakeup_src); } if ((src->input == TSPP2_INPUT_TSIF0) || (src->input == TSPP2_INPUT_TSIF1)) { tspp2_tsif_start(&src->device->tsif_devices[src->input]); reg = readl_relaxed(src->device->base + TSPP2_TSIF_INPUT_SRC_CONFIG(src->input)); reg |= (0x1 << TSIF_INPUT_SRC_CONFIG_INPUT_EN_OFFS); writel_relaxed(reg, src->device->base + TSPP2_TSIF_INPUT_SRC_CONFIG(src->input)); } else { reg = readl_relaxed(src->device->base + TSPP2_MEM_INPUT_SRC_CONFIG(src->hw_index)); reg |= (0x1 << MEM_INPUT_SRC_CONFIG_INPUT_EN_OFFS); writel_relaxed(reg, src->device->base + TSPP2_MEM_INPUT_SRC_CONFIG(src->hw_index)); } src->enabled = 1; src->device->num_enabled_sources++; mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); dev_dbg(src->device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_src_enable); /** * tspp2_src_disable() - Disable source. * * @src_handle: Source to disable. * * Return 0 on success, error value otherwise. */ int tspp2_src_disable(u32 src_handle) { struct tspp2_src *src = (struct tspp2_src *)src_handle; int ret; if (!src) { pr_err("%s: Invalid source handle\n", __func__); return -EINVAL; } ret = pm_runtime_get_sync(src->device->dev); if (ret < 0) return ret; mutex_lock(&src->device->mutex); if (!src->device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EPERM; } ret = tspp2_src_disable_internal(src); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); if (!ret) dev_dbg(src->device->dev, "%s: successful\n", __func__); return ret; } EXPORT_SYMBOL(tspp2_src_disable); /** * tspp2_filter_ops_clear() - Clear filter operations database and HW * * @filter: The filter to work on. */ static void tspp2_filter_ops_clear(struct tspp2_filter *filter) { int i; /* Set all filter operations in HW to Exit operation */ for (i = 0; i < TSPP2_MAX_OPS_PER_FILTER; i++) { writel_relaxed(TSPP2_OPCODE_EXIT, filter->device->base + TSPP2_OPCODE(filter->hw_index, i)); } memset(filter->operations, 0, (sizeof(struct tspp2_operation) * TSPP2_MAX_OPS_PER_FILTER)); filter->num_user_operations = 0; filter->indexing_op_set = 0; filter->raw_op_with_indexing = 0; filter->pes_analysis_op_set = 0; filter->raw_op_set = 0; filter->pes_tx_op_set = 0; } /** * tspp2_filter_context_reset() - Reset filter context and release it. * * @filter: The filter to work on. */ static void tspp2_filter_context_reset(struct tspp2_filter *filter) { /* Reset this filter's context. Each register handles 32 contexts */ writel_relaxed((0x1 << TSPP2_MODULUS_OP(filter->context, 32)), filter->device->base + TSPP2_TSP_CONTEXT_RESET(filter->context >> 5)); writel_relaxed((0x1 << TSPP2_MODULUS_OP(filter->context, 32)), filter->device->base + TSPP2_PES_CONTEXT_RESET(filter->context >> 5)); writel_relaxed((0x1 << TSPP2_MODULUS_OP(filter->context, 32)), filter->device->base + TSPP2_INDEXING_CONTEXT_RESET(filter->context >> 5)); writel_relaxed(0, filter->device->base + TSPP2_FILTER_ENTRY1(filter->hw_index)); /* Release context */ filter->device->contexts[filter->context] = 0; } /** * tspp2_filter_sw_reset() - Reset filter SW fields helper function. * * @filter: The filter to work on. */ static void tspp2_filter_sw_reset(struct tspp2_filter *filter) { unsigned long flags; /* * All fields are cleared when opening a filter. Still it is important * to reset some of the fields here, specifically to set opened to 0 and * also to set the callback to NULL. */ filter->opened = 0; filter->src = NULL; filter->batch = NULL; filter->context = 0; filter->hw_index = 0; filter->pid_value = 0; filter->mask = 0; spin_lock_irqsave(&filter->device->spinlock, flags); filter->event_callback = NULL; filter->event_cookie = NULL; filter->event_bitmask = 0; spin_unlock_irqrestore(&filter->device->spinlock, flags); filter->enabled = 0; } /** * tspp2_src_batch_set() - Set/clear a filter batch to/from a source. * * @src: The source to work on. * @batch_id: The batch to set/clear. * @set: Set/clear flag. */ static void tspp2_src_batch_set(struct tspp2_src *src, u8 batch_id, int set) { u32 reg = 0; if (src->input == TSPP2_INPUT_MEMORY) { reg = readl_relaxed(src->device->base + TSPP2_MEM_INPUT_SRC_CONFIG(src->hw_index)); if (set) reg |= ((1 << batch_id) << MEM_INPUT_SRC_CONFIG_BATCHES_OFFS); else reg &= ~((1 << batch_id) << MEM_INPUT_SRC_CONFIG_BATCHES_OFFS); writel_relaxed(reg, src->device->base + TSPP2_MEM_INPUT_SRC_CONFIG(src->hw_index)); } else { reg = readl_relaxed(src->device->base + TSPP2_TSIF_INPUT_SRC_CONFIG(src->input)); if (set) reg |= ((1 << batch_id) << TSIF_INPUT_SRC_CONFIG_BATCHES_OFFS); else reg &= ~((1 << batch_id) << TSIF_INPUT_SRC_CONFIG_BATCHES_OFFS); writel_relaxed(reg, src->device->base + TSPP2_TSIF_INPUT_SRC_CONFIG(src->input)); } } /** * tspp2_src_filters_clear() - Clear all filters from a source. * * @src_handle: Source to clear all filters from. * * Return 0 on success, error value otherwise. */ int tspp2_src_filters_clear(u32 src_handle) { int ret; int i; struct tspp2_filter *filter = NULL; struct tspp2_filter *tmp_filter; struct tspp2_filter_batch *batch = NULL; struct tspp2_filter_batch *tmp_batch; struct tspp2_src *src = (struct tspp2_src *)src_handle; if (!src) { pr_err("%s: Invalid source handle\n", __func__); return -EINVAL; } ret = pm_runtime_get_sync(src->device->dev); if (ret < 0) return ret; mutex_lock(&src->device->mutex); if (!src->device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EPERM; } if (!src->opened) { pr_err("%s: Source not opened\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EINVAL; } /* Go over filters in source, disable them, clear their operations, * "close" them (similar to tspp2_filter_close function but simpler). * No need to worry about cases of reserved filter, so just clear * filters HW- and SW-wise. Then update source's filters and batches * lists and numbers. Simple :) */ list_for_each_entry_safe(filter, tmp_filter, &src->filters_list, link) { /* Disable filter */ writel_relaxed(0, filter->device->base + TSPP2_FILTER_ENTRY0(filter->hw_index)); /* Clear filter operations in HW as well as related SW fields */ tspp2_filter_ops_clear(filter); /* Reset filter context-based counters */ tspp2_filter_counters_reset(filter->device, filter->context); /* Reset filter context and release it back to the device */ tspp2_filter_context_reset(filter); /* Reset filter SW fields */ tspp2_filter_sw_reset(filter); list_del(&filter->link); } list_for_each_entry_safe(batch, tmp_batch, &src->batches_list, link) { tspp2_src_batch_set(src, batch->batch_id, 0); for (i = 0; i < TSPP2_FILTERS_PER_BATCH; i++) batch->hw_filters[i] = 0; batch->src = NULL; list_del(&batch->link); } src->num_associated_batches = 0; src->num_associated_filters = 0; src->reserved_filter_hw_index = 0; mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); dev_dbg(src->device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_src_filters_clear); /* Filters and Operations API functions */ /** * tspp2_filter_open() - Open a new filter and add it to a source. * * @src_handle: Source to add the new filter to. * @pid: Filter's 13-bit PID value. * @mask: Filter's 13-bit mask. Note it is highly recommended * to use a full bit mask of 0x1FFF, so the filter * operates on a unique PID. * @filter_handle: Opened filter handle. * * Return 0 on success, error value otherwise. */ int tspp2_filter_open(u32 src_handle, u16 pid, u16 mask, u32 *filter_handle) { struct tspp2_src *src = (struct tspp2_src *)src_handle; struct tspp2_filter_batch *batch; struct tspp2_filter *filter = NULL; u16 hw_idx; int i; u32 reg = 0; int found = 0; int ret; if (!src) { pr_err("%s: Invalid source handle\n", __func__); return -EINVAL; } if (!filter_handle) { pr_err("%s: Invalid filter handle pointer\n", __func__); return -EINVAL; } if ((pid & ~0x1FFF) || (mask & ~0x1FFF)) { pr_err("%s: Invalid PID or mask values (13 bits available)\n", __func__); return -EINVAL; } ret = pm_runtime_get_sync(src->device->dev); if (ret < 0) return ret; if (mutex_lock_interruptible(&src->device->mutex)) { pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -ERESTARTSYS; } if (!src->device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EPERM; } if (!src->opened) { pr_err("%s: Source not opened\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EINVAL; } /* Find an available filter object in the device's filters database */ for (i = 0; i < TSPP2_NUM_AVAIL_FILTERS; i++) if (!src->device->filters[i].opened) break; if (i == TSPP2_NUM_AVAIL_FILTERS) { pr_err("%s: No available filters\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -ENOMEM; } filter = &src->device->filters[i]; /* Find an available context. Each new filter needs a unique context */ for (i = 0; i < TSPP2_NUM_AVAIL_CONTEXTS; i++) if (!src->device->contexts[i]) break; if (i == TSPP2_NUM_AVAIL_CONTEXTS) { pr_err("%s: No available filters\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -ENOMEM; } src->device->contexts[i] = 1; filter->context = i; if (src->num_associated_batches) { /* * Look for an available HW filter among the batches * already associated with this source. */ list_for_each_entry(batch, &src->batches_list, link) { for (i = 0; i < TSPP2_FILTERS_PER_BATCH; i++) { hw_idx = (batch->batch_id * TSPP2_FILTERS_PER_BATCH) + i; if ((hw_idx != src->reserved_filter_hw_index) && (batch->hw_filters[i] == 0)) break; } if (i < TSPP2_FILTERS_PER_BATCH) { /* Found an available HW filter */ batch->hw_filters[i] = 1; found = 1; break; } } } if (!found) { /* Either the source did not have any associated batches, * or we could not find an available HW filter in any of * the source's batches. In any case, we need to find a new * batch. Then we use the first filter in this batch. */ for (i = 0; i < TSPP2_NUM_BATCHES; i++) { if (!src->device->batches[i].src) { src->device->batches[i].src = src; batch = &src->device->batches[i]; batch->hw_filters[0] = 1; hw_idx = (batch->batch_id * TSPP2_FILTERS_PER_BATCH); break; } } if (i == TSPP2_NUM_BATCHES) { pr_err("%s: No available filters\n", __func__); src->device->contexts[filter->context] = 0; filter->context = 0; mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -ENOMEM; } tspp2_src_batch_set(src, batch->batch_id, 1); list_add_tail(&batch->link, &src->batches_list); /* Update reserved filter index only when needed */ if (src->num_associated_batches == 0) { src->reserved_filter_hw_index = (batch->batch_id * TSPP2_FILTERS_PER_BATCH) + TSPP2_FILTERS_PER_BATCH - 1; } src->num_associated_batches++; } filter->opened = 1; filter->src = src; filter->batch = batch; filter->hw_index = hw_idx; filter->pid_value = pid; filter->mask = mask; filter->indexing_table_id = 0; tspp2_filter_ops_clear(filter); filter->event_callback = NULL; filter->event_cookie = NULL; filter->event_bitmask = 0; filter->enabled = 0; /* device back-pointer is already initialized, always remains valid */ list_add_tail(&filter->link, &src->filters_list); src->num_associated_filters++; /* Reset filter context-based counters */ tspp2_filter_counters_reset(filter->device, filter->context); /* Reset this filter's context */ writel_relaxed((0x1 << TSPP2_MODULUS_OP(filter->context, 32)), filter->device->base + TSPP2_TSP_CONTEXT_RESET(filter->context >> 5)); writel_relaxed((0x1 << TSPP2_MODULUS_OP(filter->context, 32)), filter->device->base + TSPP2_PES_CONTEXT_RESET(filter->context >> 5)); writel_relaxed((0x1 << TSPP2_MODULUS_OP(filter->context, 32)), filter->device->base + TSPP2_INDEXING_CONTEXT_RESET(filter->context >> 5)); /* Write PID and mask */ reg = ((pid << FILTER_ENTRY0_PID_OFFS) | (mask << FILTER_ENTRY0_MASK_OFFS)); writel_relaxed(reg, filter->device->base + TSPP2_FILTER_ENTRY0(filter->hw_index)); writel_relaxed((filter->context << FILTER_ENTRY1_CONTEXT_OFFS), filter->device->base + TSPP2_FILTER_ENTRY1(filter->hw_index)); *filter_handle = (u32)filter; mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); dev_dbg(src->device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_filter_open); /** * tspp2_hw_filters_in_batch() - Check for used HW filters in a batch. * * @batch: The filter batch to check. * * Helper function to check if there are any HW filters used on this batch. * * Return 1 if found a used filter in this batch, 0 otherwise. */ static inline int tspp2_hw_filters_in_batch(struct tspp2_filter_batch *batch) { int i; for (i = 0; i < TSPP2_FILTERS_PER_BATCH; i++) if (batch->hw_filters[i] == 1) return 1; return 0; } /** * tspp2_filter_close() - Close a filter. * * @filter_handle: Filter to close. * * Return 0 on success, error value otherwise. */ int tspp2_filter_close(u32 filter_handle) { int i; int ret; struct tspp2_device *device; struct tspp2_src *src = NULL; struct tspp2_filter_batch *batch = NULL; struct tspp2_filter_batch *tmp_batch; struct tspp2_filter *filter = (struct tspp2_filter *)filter_handle; if (!filter) { pr_err("%s: Invalid filter handle\n", __func__); return -EINVAL; } device = filter->device; ret = pm_runtime_get_sync(device->dev); if (ret < 0) return ret; mutex_lock(&device->mutex); if (!device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&device->mutex); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); return -EPERM; } if (!filter->opened) { pr_err("%s: Filter already closed\n", __func__); mutex_unlock(&device->mutex); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); return -EINVAL; } if (filter->num_user_operations) pr_warn("%s: Closing filters that has %d operations\n", __func__, filter->num_user_operations); /* Disable filter */ writel_relaxed(0, device->base + TSPP2_FILTER_ENTRY0(filter->hw_index)); /* Clear filter operations in HW as well as related SW fields */ tspp2_filter_ops_clear(filter); /* Reset filter context-based counters */ tspp2_filter_counters_reset(device, filter->context); /* Reset filter context and release it back to the device */ tspp2_filter_context_reset(filter); /* Mark filter as unused in batch */ filter->batch->hw_filters[(filter->hw_index - (filter->batch->batch_id * TSPP2_FILTERS_PER_BATCH))] = 0; /* Remove filter from source */ list_del(&filter->link); filter->src->num_associated_filters--; /* We may need to update the reserved filter for this source. * Cases to handle: * 1. This is the last filter on this source. * 2. This is the last filter on this batch + reserved filter is not on * this batch. * 3. This is the last filter on this batch + reserved filter is on this * batch. Can possibly move reserved filter to another batch if space is * available. * 4. This is not the last filter on this batch. The reserved filter may * be the only one taking another batch and may be moved to this batch * to save space. */ src = filter->src; /* * Case #1: this could be the last filter associated with this source. * If this is the case, we can release the batch too. We don't care * about the reserved HW filter index, since there are no more filters. */ if (src->num_associated_filters == 0) { filter->batch->src = NULL; list_del(&filter->batch->link); src->num_associated_batches--; tspp2_src_batch_set(src, filter->batch->batch_id, 0); src->reserved_filter_hw_index = 0; goto filter_clear; } /* * If this is the last filter that was used in this batch, we may be * able to release this entire batch. However, we have to make sure the * reserved filter is not in this batch. If it is, we may find a place * for it in another batch in this source. */ if (!tspp2_hw_filters_in_batch(filter->batch)) { /* There are no more used filters on this batch */ if ((src->reserved_filter_hw_index < (filter->batch->batch_id * TSPP2_FILTERS_PER_BATCH)) || (src->reserved_filter_hw_index >= ((filter->batch->batch_id * TSPP2_FILTERS_PER_BATCH) + TSPP2_FILTERS_PER_BATCH))) { /* Case #2: the reserved filter is not on this batch */ filter->batch->src = NULL; list_del(&filter->batch->link); src->num_associated_batches--; tspp2_src_batch_set(src, filter->batch->batch_id, 0); } else { /* * Case #3: see if we can "move" the reserved filter to * a different batch. */ list_for_each_entry_safe(batch, tmp_batch, &src->batches_list, link) { if (batch == filter->batch) continue; for (i = 0; i < TSPP2_FILTERS_PER_BATCH; i++) { if (batch->hw_filters[i] == 0) { src->reserved_filter_hw_index = (batch->batch_id * TSPP2_FILTERS_PER_BATCH) + i; filter->batch->src = NULL; list_del(&filter->batch->link); src->num_associated_batches--; tspp2_src_batch_set(src, filter->batch->batch_id, 0); goto filter_clear; } } } } } else { /* Case #4: whenever we remove a filter, there is always a * chance that the reserved filter was the only filter used on a * different batch. So now this is a good opportunity to check * if we can release that batch and use the index of the filter * we're freeing instead. */ list_for_each_entry_safe(batch, tmp_batch, &src->batches_list, link) { if (((src->reserved_filter_hw_index >= (batch->batch_id * TSPP2_FILTERS_PER_BATCH)) && (src->reserved_filter_hw_index < (batch->batch_id * TSPP2_FILTERS_PER_BATCH + TSPP2_FILTERS_PER_BATCH))) && !tspp2_hw_filters_in_batch(batch)) { src->reserved_filter_hw_index = filter->hw_index; batch->src = NULL; list_del(&batch->link); src->num_associated_batches--; tspp2_src_batch_set(src, batch->batch_id, 0); break; } } } filter_clear: tspp2_filter_sw_reset(filter); mutex_unlock(&device->mutex); pm_runtime_mark_last_busy(device->dev); pm_runtime_put_autosuspend(device->dev); dev_dbg(device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_filter_close); /** * tspp2_filter_enable() - Enable a filter. * * @filter_handle: Filter to enable. * * Return 0 on success, error value otherwise. */ int tspp2_filter_enable(u32 filter_handle) { struct tspp2_filter *filter = (struct tspp2_filter *)filter_handle; u32 reg; int ret; if (!filter) { pr_err("%s: Invalid filter handle\n", __func__); return -EINVAL; } ret = pm_runtime_get_sync(filter->device->dev); if (ret < 0) return ret; if (mutex_lock_interruptible(&filter->device->mutex)) { pm_runtime_mark_last_busy(filter->device->dev); pm_runtime_put_autosuspend(filter->device->dev); return -ERESTARTSYS; } if (!filter->device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&filter->device->mutex); pm_runtime_mark_last_busy(filter->device->dev); pm_runtime_put_autosuspend(filter->device->dev); return -EPERM; } if (!filter->opened) { pr_err("%s: Filter not opened\n", __func__); mutex_unlock(&filter->device->mutex); pm_runtime_mark_last_busy(filter->device->dev); pm_runtime_put_autosuspend(filter->device->dev); return -EINVAL; } if (filter->enabled) { pr_warn("%s: Filter already enabled\n", __func__); mutex_unlock(&filter->device->mutex); pm_runtime_mark_last_busy(filter->device->dev); pm_runtime_put_autosuspend(filter->device->dev); return 0; } reg = readl_relaxed(filter->device->base + TSPP2_FILTER_ENTRY0(filter->hw_index)); reg |= (0x1 << FILTER_ENTRY0_EN_OFFS); writel_relaxed(reg, filter->device->base + TSPP2_FILTER_ENTRY0(filter->hw_index)); filter->enabled = 1; mutex_unlock(&filter->device->mutex); pm_runtime_mark_last_busy(filter->device->dev); pm_runtime_put_autosuspend(filter->device->dev); dev_dbg(filter->device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_filter_enable); /** * tspp2_filter_disable() - Disable a filter. * * @filter_handle: Filter to disable. * * Return 0 on success, error value otherwise. */ int tspp2_filter_disable(u32 filter_handle) { struct tspp2_filter *filter = (struct tspp2_filter *)filter_handle; u32 reg; int ret; if (!filter) { pr_err("%s: Invalid filter handle\n", __func__); return -EINVAL; } ret = pm_runtime_get_sync(filter->device->dev); if (ret < 0) return ret; mutex_lock(&filter->device->mutex); if (!filter->device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&filter->device->mutex); pm_runtime_mark_last_busy(filter->device->dev); pm_runtime_put_autosuspend(filter->device->dev); return -EPERM; } if (!filter->opened) { pr_err("%s: Filter not opened\n", __func__); mutex_unlock(&filter->device->mutex); pm_runtime_mark_last_busy(filter->device->dev); pm_runtime_put_autosuspend(filter->device->dev); return -EINVAL; } if (!filter->enabled) { pr_warn("%s: Filter already disabled\n", __func__); mutex_unlock(&filter->device->mutex); pm_runtime_mark_last_busy(filter->device->dev); pm_runtime_put_autosuspend(filter->device->dev); return 0; } reg = readl_relaxed(filter->device->base + TSPP2_FILTER_ENTRY0(filter->hw_index)); reg &= ~(0x1 << FILTER_ENTRY0_EN_OFFS); writel_relaxed(reg, filter->device->base + TSPP2_FILTER_ENTRY0(filter->hw_index)); /* * HW requires we wait for up to 2ms here before closing the pipes * used by this filter */ udelay(TSPP2_HW_DELAY_USEC); filter->enabled = 0; mutex_unlock(&filter->device->mutex); pm_runtime_mark_last_busy(filter->device->dev); pm_runtime_put_autosuspend(filter->device->dev); dev_dbg(filter->device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_filter_disable); /** * tspp2_pes_analysis_op_write() - Write a PES Analysis operation. * * @filter: The filter to set the operation to. * @op: The operation. * @op_index: The operation's index in this filter. * * Return 0 on success, error value otherwise. */ static int tspp2_pes_analysis_op_write(struct tspp2_filter *filter, const struct tspp2_operation *op, u8 op_index) { u32 reg = 0; if (filter->mask != TSPP2_UNIQUE_PID_MASK) { pr_err( "%s: A filter with a PES Analysis operation must handle a unique PID\n", __func__); return -EINVAL; } /* * Bits[19:6] = 0, Bit[5] = Source, * Bit[4] = Skip, Bits[3:0] = Opcode */ reg |= TSPP2_OPCODE_PES_ANALYSIS; if (op->params.pes_analysis.skip_ts_errs) reg |= (0x1 << 4); if (op->params.pes_analysis.input == TSPP2_OP_BUFFER_B) reg |= (0x1 << 5); filter->pes_analysis_op_set = 1; writel_relaxed(reg, filter->device->base + TSPP2_OPCODE(filter->hw_index, op_index)); dev_dbg(filter->device->dev, "%s: successful\n", __func__); return 0; } /** * tspp2_raw_tx_op_write() - Write a RAW Transmit operation. * * @filter: The filter to set the operation to. * @op: The operation. * @op_index: The operation's index in this filter. * * Return 0 on success, error value otherwise. */ static int tspp2_raw_tx_op_write(struct tspp2_filter *filter, const struct tspp2_operation *op, u8 op_index) { u32 reg = 0; int timestamp = 0; struct tspp2_pipe *pipe = (struct tspp2_pipe *) op->params.raw_transmit.output_pipe_handle; if (!pipe || !pipe->opened) { pr_err("%s: Invalid pipe handle\n", __func__); return -EINVAL; } /* * Bits[19:16] = 0, Bit[15] = Support Indexing, * Bit[14] = Timestamp position, * Bits[13:12] = Timestamp mode, * Bits[11:6] = Output pipe, Bit[5] = Source, * Bit[4] = Skip, Bits[3:0] = Opcode */ reg |= TSPP2_OPCODE_RAW_TRANSMIT; if (op->params.raw_transmit.skip_ts_errs) reg |= (0x1 << 4); if (op->params.raw_transmit.input == TSPP2_OP_BUFFER_B) reg |= (0x1 << 5); reg |= ((pipe->hw_index & 0x3F) << 6); switch (op->params.raw_transmit.timestamp_mode) { case TSPP2_OP_TIMESTAMP_NONE: /* nothing to do, keep bits value as 0 */ break; case TSPP2_OP_TIMESTAMP_ZERO: reg |= (0x1 << 12); timestamp = 1; break; case TSPP2_OP_TIMESTAMP_STC: reg |= (0x2 << 12); timestamp = 1; break; default: pr_err("%s: Invalid timestamp mode\n", __func__); return -EINVAL; } if (timestamp && op->params.raw_transmit.timestamp_position == TSPP2_PACKET_FORMAT_188_RAW) { pr_err("%s: Invalid timestamp position\n", __func__); return -EINVAL; } if (op->params.raw_transmit.timestamp_position == TSPP2_PACKET_FORMAT_192_TAIL) reg |= (0x1 << 14); if (op->params.raw_transmit.support_indexing) { if (filter->raw_op_with_indexing) { pr_err( "%s: Only one Raw Transmit operation per filter can support HW indexing\n", __func__); return -EINVAL; } filter->raw_op_with_indexing = 1; reg |= (0x1 << 15); } filter->raw_op_set = 1; writel_relaxed(reg, filter->device->base + TSPP2_OPCODE(filter->hw_index, op_index)); dev_dbg(filter->device->dev, "%s: successful\n", __func__); return 0; } /** * tspp2_pes_tx_op_write() - Write a PES Transmit operation. * * @filter: The filter to set the operation to. * @op: The operation. * @op_index: The operation's index in this filter. * * Return 0 on success, error value otherwise. */ static int tspp2_pes_tx_op_write(struct tspp2_filter *filter, const struct tspp2_operation *op, u8 op_index) { u32 reg = 0; struct tspp2_pipe *payload_pipe = (struct tspp2_pipe *) op->params.pes_transmit.output_pipe_handle; struct tspp2_pipe *header_pipe; if (!payload_pipe || !payload_pipe->opened) { pr_err("%s: Invalid payload pipe handle\n", __func__); return -EINVAL; } if (!filter->pes_analysis_op_set) { pr_err( "%s: PES Analysys operation must precede any PES Transmit operation\n", __func__); return -EINVAL; } /* * Bits[19:18] = 0, Bits[17:12] = PES Header output pipe, * Bits[11:6] = Output pipe, Bit[5] = Source, * Bit[4] = Attach STC and flags, * Bit[3] = Disable TX on PES discontinuity, * Bit[2] = Enable SW indexing, Bit[1] = Mode, Bit[0] = 0 */ if (op->params.pes_transmit.mode == TSPP2_OP_PES_TRANSMIT_FULL) { reg |= (0x1 << 1); } else { /* Separated PES mode requires another pipe */ header_pipe = (struct tspp2_pipe *) op->params.pes_transmit.header_output_pipe_handle; if (!header_pipe || !header_pipe->opened) { pr_err("%s: Invalid header pipe handle\n", __func__); return -EINVAL; } reg |= ((header_pipe->hw_index & 0x3F) << 12); } if (op->params.pes_transmit.enable_sw_indexing) { if (!filter->raw_op_set) { pr_err( "%s: PES Transmit operation with SW indexing must be preceded by a Raw Transmit operation\n", __func__); return -EINVAL; } reg |= (0x1 << 2); } if (op->params.pes_transmit.disable_tx_on_pes_discontinuity) reg |= (0x1 << 3); if (op->params.pes_transmit.attach_stc_flags) reg |= (0x1 << 4); if (op->params.pes_transmit.input == TSPP2_OP_BUFFER_B) reg |= (0x1 << 5); reg |= ((payload_pipe->hw_index & 0x3F) << 6); filter->pes_tx_op_set = 1; writel_relaxed(reg, filter->device->base + TSPP2_OPCODE(filter->hw_index, op_index)); dev_dbg(filter->device->dev, "%s: successful\n", __func__); return 0; } /** * tspp2_pcr_op_write() - Write a PCR Extraction operation. * * @filter: The filter to set the operation to. * @op: The operation. * @op_index: The operation's index in this filter. * * Return 0 on success, error value otherwise. */ static int tspp2_pcr_op_write(struct tspp2_filter *filter, const struct tspp2_operation *op, u8 op_index) { u32 reg = 0; struct tspp2_pipe *pipe = (struct tspp2_pipe *) op->params.pcr_extraction.output_pipe_handle; if (!pipe || !pipe->opened) { pr_err("%s: Invalid pipe handle\n", __func__); return -EINVAL; } if (!op->params.pcr_extraction.extract_pcr && !op->params.pcr_extraction.extract_opcr && !op->params.pcr_extraction.extract_splicing_point && !op->params.pcr_extraction.extract_transport_private_data && !op->params.pcr_extraction.extract_af_extension && !op->params.pcr_extraction.extract_all_af) { pr_err("%s: Invalid extraction parameters\n", __func__); return -EINVAL; } /* * Bits[19:18] = 0, Bit[17] = All AF, Bit[16] = AF Extension, * Bit[15] = Transport Priave Data, Bit[14] = Splicing Point, * Bit[13] = OPCR, Bit[12] = PCR, Bits[11:6] = Output pipe, * Bit[5] = Source, Bit[4] = Skip, Bits[3:0] = Opcode */ reg |= TSPP2_OPCODE_PCR_EXTRACTION; if (op->params.pcr_extraction.skip_ts_errs) reg |= (0x1 << 4); if (op->params.pcr_extraction.input == TSPP2_OP_BUFFER_B) reg |= (0x1 << 5); reg |= ((pipe->hw_index & 0x3F) << 6); if (op->params.pcr_extraction.extract_pcr) reg |= (0x1 << 12); if (op->params.pcr_extraction.extract_opcr) reg |= (0x1 << 13); if (op->params.pcr_extraction.extract_splicing_point) reg |= (0x1 << 14); if (op->params.pcr_extraction.extract_transport_private_data) reg |= (0x1 << 15); if (op->params.pcr_extraction.extract_af_extension) reg |= (0x1 << 16); if (op->params.pcr_extraction.extract_all_af) reg |= (0x1 << 17); writel_relaxed(reg, filter->device->base + TSPP2_OPCODE(filter->hw_index, op_index)); dev_dbg(filter->device->dev, "%s: successful\n", __func__); return 0; } /** * tspp2_cipher_op_write() - Write a Cipher operation. * * @filter: The filter to set the operation to. * @op: The operation. * @op_index: The operation's index in this filter. * * Return 0 on success, error value otherwise. */ static int tspp2_cipher_op_write(struct tspp2_filter *filter, const struct tspp2_operation *op, u8 op_index) { u32 reg = 0; /* * Bits[19:18] = 0, Bits[17:15] = Scrambling related, * Bit[14] = Mode, Bit[13] = Decrypt PES header, * Bits[12:7] = Key ladder index, Bit[6] = Destination, * Bit[5] = Source, Bit[4] = Skip, Bits[3:0] = Opcode */ reg |= TSPP2_OPCODE_CIPHER; if (op->params.cipher.skip_ts_errs) reg |= (0x1 << 4); if (op->params.cipher.input == TSPP2_OP_BUFFER_B) reg |= (0x1 << 5); if (op->params.cipher.output == TSPP2_OP_BUFFER_B) reg |= (0x1 << 6); reg |= ((op->params.cipher.key_ladder_index & 0x3F) << 7); if (op->params.cipher.mode == TSPP2_OP_CIPHER_ENCRYPT && op->params.cipher.decrypt_pes_header) { pr_err("%s: Invalid parameters\n", __func__); return -EINVAL; } if (op->params.cipher.decrypt_pes_header) reg |= (0x1 << 13); if (op->params.cipher.mode == TSPP2_OP_CIPHER_ENCRYPT) reg |= (0x1 << 14); switch (op->params.cipher.scrambling_mode) { case TSPP2_OP_CIPHER_AS_IS: reg |= (0x1 << 15); break; case TSPP2_OP_CIPHER_SET_SCRAMBLING_0: /* nothing to do, keep bits[17:16] as 0 */ break; case TSPP2_OP_CIPHER_SET_SCRAMBLING_1: reg |= (0x1 << 16); break; case TSPP2_OP_CIPHER_SET_SCRAMBLING_2: reg |= (0x2 << 16); break; case TSPP2_OP_CIPHER_SET_SCRAMBLING_3: reg |= (0x3 << 16); break; default: pr_err("%s: Invalid scrambling mode\n", __func__); return -EINVAL; } writel_relaxed(reg, filter->device->base + TSPP2_OPCODE(filter->hw_index, op_index)); dev_dbg(filter->device->dev, "%s: successful\n", __func__); return 0; } /** * tspp2_index_op_write() - Write an Indexing operation. * * @filter: The filter to set the operation to. * @op: The operation. * @op_index: The operation's index in this filter. * * Return 0 on success, error value otherwise. */ static int tspp2_index_op_write(struct tspp2_filter *filter, const struct tspp2_operation *op, u8 op_index) { u32 reg = 0; u32 filter_reg = 0; struct tspp2_pipe *pipe = (struct tspp2_pipe *) op->params.indexing.output_pipe_handle; if (!pipe || !pipe->opened) { pr_err("%s: Invalid pipe handle\n", __func__); return -EINVAL; } /* Enforce Indexing related HW restrictions */ if (filter->indexing_op_set) { pr_err( "%s: Only one indexing operation supported per filter\n", __func__); return -EINVAL; } if (!filter->raw_op_with_indexing) { pr_err( "%s: Raw Transmit operation with indexing support must be configured before the Indexing operation\n", __func__); return -EINVAL; } if (!filter->pes_analysis_op_set) { pr_err( "%s: PES Analysis operation must precede Indexing operation\n", __func__); return -EINVAL; } /* * Bits [19:15] = 0, Bit[14] = Index by RAI, * Bits[13:12] = 0, * Bits[11:6] = Output pipe, Bit[5] = Source, * Bit[4] = Skip, Bits[3:0] = Opcode */ reg |= TSPP2_OPCODE_INDEXING; if (op->params.indexing.skip_ts_errs) reg |= (0x1 << 4); if (op->params.indexing.input == TSPP2_OP_BUFFER_B) reg |= (0x1 << 5); reg |= ((pipe->hw_index & 0x3F) << 6); if (op->params.indexing.random_access_indicator_indexing) reg |= (0x1 << 14); /* Indexing table ID is set in the filter and not in the operation */ filter->indexing_table_id = op->params.indexing.indexing_table_id; filter_reg = readl_relaxed(filter->device->base + TSPP2_FILTER_ENTRY0(filter->hw_index)); filter_reg &= ~(0x3 << FILTER_ENTRY0_CODEC_OFFS); filter_reg |= (filter->indexing_table_id << FILTER_ENTRY0_CODEC_OFFS); writel_relaxed(filter_reg, filter->device->base + TSPP2_FILTER_ENTRY0(filter->hw_index)); filter->indexing_op_set = 1; writel_relaxed(reg, filter->device->base + TSPP2_OPCODE(filter->hw_index, op_index)); dev_dbg(filter->device->dev, "%s: successful\n", __func__); return 0; } /** * tspp2_copy_op_write() - Write an Copy operation. * * @filter: The filter to set the operation to. * @op: The operation. * @op_index: The operation's index in this filter. * * Return 0 on success, error value otherwise. */ static int tspp2_copy_op_write(struct tspp2_filter *filter, const struct tspp2_operation *op, u8 op_index) { u32 reg = 0; /* Bits[19:6] = 0, Bit[5] = Source, Bit[4] = 0, Bits[3:0] = Opcode */ reg |= TSPP2_OPCODE_COPY_PACKET; if (op->params.copy_packet.input == TSPP2_OP_BUFFER_B) reg |= (0x1 << 5); writel_relaxed(reg, filter->device->base + TSPP2_OPCODE(filter->hw_index, op_index)); dev_dbg(filter->device->dev, "%s: successful\n", __func__); return 0; } /** * tspp2_op_write() - Write an operation of any type. * * @filter: The filter to set the operation to. * @op: The operation. * @op_index: The operation's index in this filter. * * Return 0 on success, error value otherwise. */ static int tspp2_op_write(struct tspp2_filter *filter, const struct tspp2_operation *op, u8 op_index) { switch (op->type) { case TSPP2_OP_PES_ANALYSIS: return tspp2_pes_analysis_op_write(filter, op, op_index); case TSPP2_OP_RAW_TRANSMIT: return tspp2_raw_tx_op_write(filter, op, op_index); case TSPP2_OP_PES_TRANSMIT: return tspp2_pes_tx_op_write(filter, op, op_index); case TSPP2_OP_PCR_EXTRACTION: return tspp2_pcr_op_write(filter, op, op_index); case TSPP2_OP_CIPHER: return tspp2_cipher_op_write(filter, op, op_index); case TSPP2_OP_INDEXING: return tspp2_index_op_write(filter, op, op_index); case TSPP2_OP_COPY_PACKET: return tspp2_copy_op_write(filter, op, op_index); default: pr_warn("%s: Unknown operation type\n", __func__); return -EINVAL; } } /** * tspp2_filter_ops_add() - Set the operations of a disabled filter. * * @filter: The filter to work on. * @op: The new operations array. * @op_index: The number of operations in the array. * * Return 0 on success, error value otherwise. */ static int tspp2_filter_ops_add(struct tspp2_filter *filter, const struct tspp2_operation *ops, u8 operations_num) { int i; int ret = 0; /* User parameter validity checks were already performed */ /* * We want to start with a clean slate here. The user may call us to * set operations several times, so need to make sure only the last call * counts. */ tspp2_filter_ops_clear(filter); /* Save user operations in filter's database */ for (i = 0; i < operations_num; i++) filter->operations[i] = ops[i]; /* Write user operations to HW */ for (i = 0; i < operations_num; i++) { ret = tspp2_op_write(filter, &ops[i], i); if (ret) goto ops_cleanup; } /* * Here we want to add the Exit operation implicitly if required, that * is, if the user provided less than TSPP2_MAX_OPS_PER_FILTER * operations. However, we already called tspp2_filter_ops_clear() * which set all the operations in HW to Exit, before writing the * actual user operations. So, no need to do it again here. * Also, if someone calls this function with operations_num == 0, * it is similar to calling tspp2_filter_operations_clear(). */ filter->num_user_operations = operations_num; dev_dbg(filter->device->dev, "%s: successful\n", __func__); return 0; ops_cleanup: pr_err("%s: Failed to set operations to filter, clearing all\n", __func__); tspp2_filter_ops_clear(filter); return ret; } /** * tspp2_filter_ops_update() - Update the operations of an enabled filter. * * This function updates the operations of an enabled filter. In fact, it is * not possible to update an existing filter without disabling it, clearing * the existing operations and setting new ones. However, if we do that, * we'll miss TS packets and not handle the stream properly, so a smooth * transition is required. * The algorithm is as follows: * 1. Find a free temporary filter object. * 2. Set the new filter's HW index to the reserved HW index. * 3. Set the operations to the new filter. This sets the operations to * the correct HW registers, based on the new HW index, and also updates * the relevant information in the temporary filter object. Later we copy this * to the actual filter object. * 4. Use the same context as the old filter (to maintain HW state). * 5. Reset parts of the context if needed. * 6. Enable the new HW filter, then disable the old filter. * 7. Update the source's reserved filter HW index. * 8. Update the filter's batch, HW index and operations-related information. * * @filter: The filter to work on. * @op: The new operations array. * @op_index: The number of operations in the array. * * Return 0 on success, error value otherwise. */ static int tspp2_filter_ops_update(struct tspp2_filter *filter, const struct tspp2_operation *ops, u8 operations_num) { int i; int ret = 0; int found = 0; u32 reg = 0; u16 hw_idx; struct tspp2_filter_batch *batch; struct tspp2_filter *tmp_filter = NULL; struct tspp2_src *src = filter->src; /* * Find an available temporary filter object in the device's * filters database. */ for (i = 0; i < TSPP2_NUM_AVAIL_FILTERS; i++) if (!src->device->filters[i].opened) break; if (i == TSPP2_NUM_AVAIL_FILTERS) { /* Should never happen */ pr_err("%s: No available filters\n", __func__); return -ENOMEM; } tmp_filter = &src->device->filters[i]; /* * Set new filter operations. We do this relatively early * in the function to avoid cleanup operations if this fails. * Since this also writes to HW, we have to set the correct HW index. */ tmp_filter->hw_index = src->reserved_filter_hw_index; /* * Need to set the mask properly to indicate if the filter handles * a unique PID. */ tmp_filter->mask = filter->mask; ret = tspp2_filter_ops_add(tmp_filter, ops, operations_num); if (ret) { tmp_filter->hw_index = 0; tmp_filter->mask = 0; return ret; } /* * Mark new filter (in fact, the new filter HW index) as used in the * appropriate batch. The batch has to be one of the batches already * associated with the source. */ list_for_each_entry(batch, &src->batches_list, link) { for (i = 0; i < TSPP2_FILTERS_PER_BATCH; i++) { hw_idx = (batch->batch_id * TSPP2_FILTERS_PER_BATCH) + i; if (hw_idx == tmp_filter->hw_index) { batch->hw_filters[i] = 1; found = 1; break; } } if (found) break; } if (!found) { pr_err("%s: Could not find matching batch\n", __func__); tspp2_filter_ops_clear(tmp_filter); tmp_filter->hw_index = 0; return -EINVAL; } /* Set the same context of the old filter to the new HW filter */ writel_relaxed((filter->context << FILTER_ENTRY1_CONTEXT_OFFS), filter->device->base + TSPP2_FILTER_ENTRY1(tmp_filter->hw_index)); /* * Reset partial context, if necessary. We want to reset a partial * context before we start using it, so if there's a new operation * that uses a context where before there was no operation that used it, * we reset that context. We need to do this before we start using the * new operation, so before we enable the new filter. * Note: there is no need to reset most of the filter's context-based * counters, because the filter keeps using the same context. The * exception is the PES error counters that we may want to reset when * resetting the entire PES context. */ if (!filter->pes_tx_op_set && tmp_filter->pes_tx_op_set) { /* PES Tx operation added */ writel_relaxed( (0x1 << TSPP2_MODULUS_OP(filter->context, 32)), filter->device->base + TSPP2_PES_CONTEXT_RESET(filter->context >> 5)); writel_relaxed(0, filter->device->base + TSPP2_FILTER_PES_ERRORS(filter->context)); } if (!filter->indexing_op_set && tmp_filter->indexing_op_set) { /* Indexing operation added */ writel_relaxed( (0x1 << TSPP2_MODULUS_OP(filter->context, 32)), filter->device->base + TSPP2_INDEXING_CONTEXT_RESET(filter->context >> 5)); } /* * Write PID and mask to new filter HW registers and enable it. * Preserve filter indexing table ID. */ reg |= (0x1 << FILTER_ENTRY0_EN_OFFS); reg |= ((filter->pid_value << FILTER_ENTRY0_PID_OFFS) | (filter->mask << FILTER_ENTRY0_MASK_OFFS)); reg |= (tmp_filter->indexing_table_id << FILTER_ENTRY0_CODEC_OFFS); writel_relaxed(reg, filter->device->base + TSPP2_FILTER_ENTRY0(tmp_filter->hw_index)); /* Disable old HW filter */ writel_relaxed(0, filter->device->base + TSPP2_FILTER_ENTRY0(filter->hw_index)); /* * HW requires we wait for up to 2ms here before removing the * operations used by this filter. */ udelay(TSPP2_HW_DELAY_USEC); tspp2_filter_ops_clear(filter); writel_relaxed(0, filter->device->base + TSPP2_FILTER_ENTRY1(filter->hw_index)); /* Mark HW filter as unused in old batch */ filter->batch->hw_filters[(filter->hw_index - (filter->batch->batch_id * TSPP2_FILTERS_PER_BATCH))] = 0; /* The new HW filter may be in a new batch, so we need to update */ filter->batch = batch; /* * Update source's reserved filter HW index, and also update the * new HW index in the filter object. */ src->reserved_filter_hw_index = filter->hw_index; filter->hw_index = tmp_filter->hw_index; /* * We've already set the new operations to HW, but we want to * update the filter object, too. tmp_filter contains all the * operations' related information we need (operations and flags). * Also, we make sure to update indexing_table_id based on the new * indexing operations. */ memcpy(filter->operations, tmp_filter->operations, (sizeof(struct tspp2_operation) * TSPP2_MAX_OPS_PER_FILTER)); filter->num_user_operations = tmp_filter->num_user_operations; filter->indexing_op_set = tmp_filter->indexing_op_set; filter->raw_op_with_indexing = tmp_filter->raw_op_with_indexing; filter->pes_analysis_op_set = tmp_filter->pes_analysis_op_set; filter->raw_op_set = tmp_filter->raw_op_set; filter->pes_tx_op_set = tmp_filter->pes_tx_op_set; filter->indexing_table_id = tmp_filter->indexing_table_id; /* * Now we can clean tmp_filter. This is really just to keep the filter * object clean. However, we don't want to use tspp2_filter_ops_clear() * because it clears the operations from HW too. */ memset(tmp_filter->operations, 0, (sizeof(struct tspp2_operation) * TSPP2_MAX_OPS_PER_FILTER)); tmp_filter->num_user_operations = 0; tmp_filter->indexing_op_set = 0; tmp_filter->raw_op_with_indexing = 0; tmp_filter->pes_analysis_op_set = 0; tmp_filter->raw_op_set = 0; tmp_filter->pes_tx_op_set = 0; tmp_filter->indexing_table_id = 0; tmp_filter->hw_index = 0; dev_dbg(filter->device->dev, "%s: successful\n", __func__); return 0; } /** * tspp2_filter_operations_set() - Set operations to a filter. * * @filter_handle: Filter to set operations to. * @ops: An array of up to TSPP2_MAX_OPS_PER_FILTER * operations. * @operations_num: Number of operations in the ops array. * * This function sets the required operations to a given filter. The filter * can either be disabled (in which case it may or may not already have some * operations set), or enabled (in which case it certainly has some oprations * set). In any case, the filter's previous operations are cleared, and the new * operations provided are set. * * In addition to some trivial parameter validity checks, the following * restrictions are enforced: * 1. A filter with a PES Analysis operation must handle a unique PID (i.e., * should have a mask that equals TSPP2_UNIQUE_PID_MASK). * 2. Only a single Raw Transmit operation per filter can support HW indexing * (i.e., can have its support_indexing configuration parameter set). * 3. A PES Analysys operation must precede any PES Transmit operation. * 4. A PES Transmit operation with SW indexing (i.e., with its * enable_sw_indexing parameter set) must be preceded by a Raw Transmit * operation. * 5. Only a single indexing operation is supported per filter. * 6. A Raw Transmit operation with indexing support must be configured before * the Indexing operation. * 7. A PES Analysis operation must precede the Indexing operation. * * Return 0 on success, error value otherwise. */ int tspp2_filter_operations_set(u32 filter_handle, const struct tspp2_operation *ops, u8 operations_num) { struct tspp2_filter *filter = (struct tspp2_filter *)filter_handle; int ret = 0; if (!filter) { pr_err("%s: Invalid filter handle\n", __func__); return -EINVAL; } if (!ops || operations_num > TSPP2_MAX_OPS_PER_FILTER || operations_num == 0) { pr_err("%s: Invalid ops parameter\n", __func__); return -EINVAL; } ret = pm_runtime_get_sync(filter->device->dev); if (ret < 0) return ret; if (mutex_lock_interruptible(&filter->device->mutex)) { pm_runtime_mark_last_busy(filter->device->dev); pm_runtime_put_autosuspend(filter->device->dev); return -ERESTARTSYS; } if (!filter->device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&filter->device->mutex); pm_runtime_mark_last_busy(filter->device->dev); pm_runtime_put_autosuspend(filter->device->dev); return -EPERM; } if (!filter->opened) { pr_err("%s: Filter not opened\n", __func__); mutex_unlock(&filter->device->mutex); pm_runtime_mark_last_busy(filter->device->dev); pm_runtime_put_autosuspend(filter->device->dev); return -EINVAL; } if (filter->enabled) ret = tspp2_filter_ops_update(filter, ops, operations_num); else ret = tspp2_filter_ops_add(filter, ops, operations_num); mutex_unlock(&filter->device->mutex); pm_runtime_mark_last_busy(filter->device->dev); pm_runtime_put_autosuspend(filter->device->dev); return ret; } EXPORT_SYMBOL(tspp2_filter_operations_set); /** * tspp2_filter_operations_clear() - Clear all operations from a filter. * * @filter_handle: Filter to clear all operations from. * * Return 0 on success, error value otherwise. */ int tspp2_filter_operations_clear(u32 filter_handle) { int ret; struct tspp2_filter *filter = (struct tspp2_filter *)filter_handle; if (!filter) { pr_err("%s: Invalid filter handle\n", __func__); return -EINVAL; } ret = pm_runtime_get_sync(filter->device->dev); if (ret < 0) return ret; mutex_lock(&filter->device->mutex); if (!filter->device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&filter->device->mutex); pm_runtime_mark_last_busy(filter->device->dev); pm_runtime_put_autosuspend(filter->device->dev); return -EPERM; } if (!filter->opened) { pr_err("%s: Filter not opened\n", __func__); mutex_unlock(&filter->device->mutex); pm_runtime_mark_last_busy(filter->device->dev); pm_runtime_put_autosuspend(filter->device->dev); return -EINVAL; } if (filter->num_user_operations == 0) { pr_warn("%s: No operations to clear from filter\n", __func__); mutex_unlock(&filter->device->mutex); pm_runtime_mark_last_busy(filter->device->dev); pm_runtime_put_autosuspend(filter->device->dev); return 0; } tspp2_filter_ops_clear(filter); mutex_unlock(&filter->device->mutex); pm_runtime_mark_last_busy(filter->device->dev); pm_runtime_put_autosuspend(filter->device->dev); dev_dbg(filter->device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_filter_operations_clear); /** * tspp2_filter_current_scrambling_bits_get() - Get the current scrambling bits. * * @filter_handle: Filter to get the scrambling bits from. * @scrambling_bits_value: The current value of the scrambling bits. * This could be the value from the TS packet * header, the value from the PES header, or a * logical OR operation of both values, depending * on the scrambling_bits_monitoring configuration * of the source this filter belongs to. * * Return 0 on success, error value otherwise. */ int tspp2_filter_current_scrambling_bits_get(u32 filter_handle, u8 *scrambling_bits_value) { struct tspp2_filter *filter = (struct tspp2_filter *)filter_handle; u32 reg; u32 ts_bits; u32 pes_bits; int ret; if (!filter) { pr_err("%s: Invalid filter handle\n", __func__); return -EINVAL; } if (scrambling_bits_value == NULL) { pr_err("%s: Invalid parameter\n", __func__); return -EINVAL; } ret = pm_runtime_get_sync(filter->device->dev); if (ret < 0) return ret; if (mutex_lock_interruptible(&filter->device->mutex)) { pm_runtime_mark_last_busy(filter->device->dev); pm_runtime_put_autosuspend(filter->device->dev); return -ERESTARTSYS; } if (!filter->device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&filter->device->mutex); pm_runtime_mark_last_busy(filter->device->dev); pm_runtime_put_autosuspend(filter->device->dev); return -EPERM; } if (!filter->opened) { pr_err("%s: Filter not opened\n", __func__); mutex_unlock(&filter->device->mutex); pm_runtime_mark_last_busy(filter->device->dev); pm_runtime_put_autosuspend(filter->device->dev); return -EINVAL; } reg = readl_relaxed(filter->device->base + TSPP2_TSP_CONTEXT(filter->context)); ts_bits = ((reg >> TSP_CONTEXT_TS_HEADER_SC_OFFS) & 0x3); pes_bits = ((reg >> TSP_CONTEXT_PES_HEADER_SC_OFFS) & 0x3); switch (filter->src->scrambling_bits_monitoring) { case TSPP2_SRC_SCRAMBLING_MONITOR_PES_ONLY: *scrambling_bits_value = pes_bits; break; case TSPP2_SRC_SCRAMBLING_MONITOR_TS_ONLY: *scrambling_bits_value = ts_bits; break; case TSPP2_SRC_SCRAMBLING_MONITOR_PES_AND_TS: *scrambling_bits_value = (pes_bits | ts_bits); break; case TSPP2_SRC_SCRAMBLING_MONITOR_NONE: /* fall through to default case */ default: pr_err("%s: Invalid scrambling bits mode\n", __func__); mutex_unlock(&filter->device->mutex); pm_runtime_mark_last_busy(filter->device->dev); pm_runtime_put_autosuspend(filter->device->dev); return -EINVAL; } mutex_unlock(&filter->device->mutex); pm_runtime_mark_last_busy(filter->device->dev); pm_runtime_put_autosuspend(filter->device->dev); dev_dbg(filter->device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_filter_current_scrambling_bits_get); /* Data-path API functions */ /** * tspp2_pipe_descriptor_get() - Get a data descriptor from a pipe. * * @pipe_handle: Pipe to get the descriptor from. * @desc: Received pipe data descriptor. * * Return 0 on success, error value otherwise. */ int tspp2_pipe_descriptor_get(u32 pipe_handle, struct sps_iovec *desc) { int ret; struct tspp2_pipe *pipe = (struct tspp2_pipe *)pipe_handle; if (!pipe) { pr_err("%s: Invalid pipe handle\n", __func__); return -EINVAL; } if (!desc) { pr_err("%s: Invalid descriptor pointer\n", __func__); return -EINVAL; } /* Descriptor pointer validity is checked inside the SPS driver. */ ret = pm_runtime_get_sync(pipe->device->dev); if (ret < 0) return ret; if (mutex_lock_interruptible(&pipe->device->mutex)) { pm_runtime_mark_last_busy(pipe->device->dev); pm_runtime_put_autosuspend(pipe->device->dev); return -ERESTARTSYS; } if (!pipe->device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&pipe->device->mutex); pm_runtime_mark_last_busy(pipe->device->dev); pm_runtime_put_autosuspend(pipe->device->dev); return -EPERM; } if (!pipe->opened) { pr_err("%s: Pipe not opened\n", __func__); mutex_unlock(&pipe->device->mutex); pm_runtime_mark_last_busy(pipe->device->dev); pm_runtime_put_autosuspend(pipe->device->dev); return -EINVAL; } ret = sps_get_iovec(pipe->sps_pipe, desc); mutex_unlock(&pipe->device->mutex); pm_runtime_mark_last_busy(pipe->device->dev); pm_runtime_put_autosuspend(pipe->device->dev); dev_dbg(pipe->device->dev, "%s: successful\n", __func__); return ret; } EXPORT_SYMBOL(tspp2_pipe_descriptor_get); /** * tspp2_pipe_descriptor_put() - Release a descriptor for reuse by the pipe. * * @pipe_handle: Pipe to release the descriptor to. * @addr: Address to release for reuse. * @size: Size to release. * @flags: Descriptor flags. * * Return 0 on success, error value otherwise. */ int tspp2_pipe_descriptor_put(u32 pipe_handle, u32 addr, u32 size, u32 flags) { int ret; struct tspp2_pipe *pipe = (struct tspp2_pipe *)pipe_handle; if (!pipe) { pr_err("%s: Invalid pipe handle\n", __func__); return -EINVAL; } ret = pm_runtime_get_sync(pipe->device->dev); if (ret < 0) return ret; if (mutex_lock_interruptible(&pipe->device->mutex)) { pm_runtime_mark_last_busy(pipe->device->dev); pm_runtime_put_autosuspend(pipe->device->dev); return -ERESTARTSYS; } if (!pipe->device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&pipe->device->mutex); pm_runtime_mark_last_busy(pipe->device->dev); pm_runtime_put_autosuspend(pipe->device->dev); return -EPERM; } if (!pipe->opened) { pr_err("%s: Pipe not opened\n", __func__); mutex_unlock(&pipe->device->mutex); pm_runtime_mark_last_busy(pipe->device->dev); pm_runtime_put_autosuspend(pipe->device->dev); return -EINVAL; } ret = sps_transfer_one(pipe->sps_pipe, addr, size, NULL, flags); mutex_unlock(&pipe->device->mutex); pm_runtime_mark_last_busy(pipe->device->dev); pm_runtime_put_autosuspend(pipe->device->dev); dev_dbg(pipe->device->dev, "%s: successful\n", __func__); return ret; } EXPORT_SYMBOL(tspp2_pipe_descriptor_put); /** * tspp2_pipe_last_address_used_get() - Get the last address the TSPP2 used. * * @pipe_handle: Pipe to get the address from. * @address: The last (virtual) address TSPP2 wrote data to. * * Return 0 on success, error value otherwise. */ int tspp2_pipe_last_address_used_get(u32 pipe_handle, u32 *address) { int ret; struct tspp2_pipe *pipe = (struct tspp2_pipe *)pipe_handle; if (!pipe) { pr_err("%s: Invalid pipe handle\n", __func__); return -EINVAL; } if (!address) { pr_err("%s: Invalid address pointer\n", __func__); return -EINVAL; } ret = pm_runtime_get_sync(pipe->device->dev); if (ret < 0) return ret; if (mutex_lock_interruptible(&pipe->device->mutex)) { pm_runtime_mark_last_busy(pipe->device->dev); pm_runtime_put_autosuspend(pipe->device->dev); return -ERESTARTSYS; } if (!pipe->device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&pipe->device->mutex); pm_runtime_mark_last_busy(pipe->device->dev); pm_runtime_put_autosuspend(pipe->device->dev); return -EPERM; } if (!pipe->opened) { pr_err("%s: Pipe not opened\n", __func__); mutex_unlock(&pipe->device->mutex); pm_runtime_mark_last_busy(pipe->device->dev); pm_runtime_put_autosuspend(pipe->device->dev); return -EINVAL; } *address = readl_relaxed(pipe->device->base + TSPP2_PIPE_LAST_ADDRESS(pipe->hw_index)); mutex_unlock(&pipe->device->mutex); pm_runtime_mark_last_busy(pipe->device->dev); pm_runtime_put_autosuspend(pipe->device->dev); *address = be32_to_cpu(*address); dev_dbg(pipe->device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_pipe_last_address_used_get); /** * tspp2_data_write() - Write (feed) data to a source. * * @src_handle: Source to feed data to. * @offset: Offset in the source's input pipe buffer. * @size: Size of data to write, in bytes. * * Schedule BAM transfers to feed data from the source's input pipe * to TSPP2 for processing. Note that the user is responsible for opening * an input pipe with the appropriate configuration parameters, and attaching * this pipe as an input pipe to the source. Pipe configuration validity is not * verified by this function. * * Return 0 on success, error value otherwise. */ int tspp2_data_write(u32 src_handle, u32 offset, u32 size) { int ret; u32 desc_length; u32 desc_flags; u32 data_length = size; u32 data_offset = offset; struct tspp2_pipe *pipe; struct tspp2_src *src = (struct tspp2_src *)src_handle; if (!src) { pr_err("%s: Invalid source handle\n", __func__); return -EINVAL; } ret = pm_runtime_get_sync(src->device->dev); if (ret < 0) return ret; if (mutex_lock_interruptible(&src->device->mutex)) { pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -ERESTARTSYS; } if (!src->device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EPERM; } if (!src->opened) { pr_err("%s: Source not opened\n", __func__); goto err_inval; } if (!src->enabled) { pr_err("%s: Source not enabled\n", __func__); goto err_inval; } if ((src->input != TSPP2_INPUT_MEMORY) || !src->input_pipe) { pr_err("%s: Invalid source input or no input pipe\n", __func__); goto err_inval; } pipe = src->input_pipe; if (offset + size > pipe->cfg.buffer_size) { pr_err("%s: offset + size > buffer size\n", __func__); goto err_inval; } while (data_length) { if (data_length > pipe->cfg.sps_cfg.descriptor_size) { desc_length = pipe->cfg.sps_cfg.descriptor_size; desc_flags = 0; } else { /* last descriptor */ desc_length = data_length; desc_flags = SPS_IOVEC_FLAG_EOT; } ret = sps_transfer_one(pipe->sps_pipe, pipe->iova + data_offset, desc_length, pipe->cfg.sps_cfg.user_info, desc_flags); if (ret) { pr_err("%s: sps_transfer_one failed, %d\n", __func__, ret); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return ret; } data_offset += desc_length; data_length -= desc_length; } mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); dev_dbg(src->device->dev, "%s: successful\n", __func__); return 0; err_inval: mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EINVAL; } EXPORT_SYMBOL(tspp2_data_write); /** * tspp2_tsif_data_write() - Write (feed) data to a TSIF source via Loopback. * * @src_handle: Source to feed data to. * @data: data buffer containing one TS packet of size 188 Bytes. * * Write one TS packet of size 188 bytes to the TSIF loopback interface. * * Return 0 on success, error value otherwise. */ int tspp2_tsif_data_write(u32 src_handle, u32 *data) { int i; int ret; struct tspp2_src *src = (struct tspp2_src *)src_handle; struct tspp2_tsif_device *tsif_device; const unsigned int loopback_flags[3] = {0x01000000, 0, 0x02000000}; if (data == NULL) { pr_err("%s: NULL data\n", __func__); return -EINVAL; } if (!src) { pr_err("%s: Invalid source handle\n", __func__); return -EINVAL; } ret = pm_runtime_get_sync(src->device->dev); if (ret < 0) return ret; if (mutex_lock_interruptible(&src->device->mutex)) { pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -ERESTARTSYS; } if (!src->device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EPERM; } if (!src->opened) { pr_err("%s: Source not opened\n", __func__); goto err_inval; } if (!src->enabled) { pr_err("%s: Source not enabled\n", __func__); goto err_inval; } if ((src->input != TSPP2_INPUT_TSIF0) && (src->input != TSPP2_INPUT_TSIF1)) { pr_err("%s: Invalid source input\n", __func__); goto err_inval; } tsif_device = &src->device->tsif_devices[src->input]; /* lpbk_flags : start && !last */ writel_relaxed(loopback_flags[0], tsif_device->base + TSPP2_TSIF_LPBK_FLAGS); /* 1-st dword of data */ writel_relaxed(data[0], tsif_device->base + TSPP2_TSIF_LPBK_DATA); /* Clear start bit */ writel_relaxed(loopback_flags[1], tsif_device->base + TSPP2_TSIF_LPBK_FLAGS); /* 45 more dwords */ for (i = 1; i < 46; i++) writel_relaxed(data[i], tsif_device->base + TSPP2_TSIF_LPBK_DATA); /* Set last bit */ writel_relaxed(loopback_flags[2], tsif_device->base + TSPP2_TSIF_LPBK_FLAGS); /* Last data dword */ writel_relaxed(data[46], tsif_device->base + TSPP2_TSIF_LPBK_DATA); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); dev_dbg(src->device->dev, "%s: successful\n", __func__); return 0; err_inval: mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EINVAL; } EXPORT_SYMBOL(tspp2_tsif_data_write); /* Event notification API functions */ /** * tspp2_global_event_notification_register() - Get notified on a global event. * * @dev_id: TSPP2 device ID. * @global_event_bitmask: A bitmask of global events, * TSPP2_GLOBAL_EVENT_XXX. * @callback: User callback function. * @cookie: User information passed to the callback. * * Register a user callback which will be invoked when certain global * events occur. Note the values (mask, callback and cookie) are overwritten * when calling this function multiple times. Therefore it is possible to * "unregister" a callback by calling this function with the bitmask set to 0 * and with NULL callback and cookie. * * Return 0 on success, error value otherwise. */ int tspp2_global_event_notification_register(u32 dev_id, u32 global_event_bitmask, void (*callback)(void *cookie, u32 event_bitmask), void *cookie) { struct tspp2_device *device; unsigned long flags; u32 reg = 0; if (dev_id >= TSPP2_NUM_DEVICES) { pr_err("%s: Invalid device ID %d\n", __func__, dev_id); return -ENODEV; } device = tspp2_devices[dev_id]; if (!device) { pr_err("%s: Invalid device\n", __func__); return -ENODEV; } if (mutex_lock_interruptible(&device->mutex)) return -ERESTARTSYS; if (!device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&device->mutex); return -EPERM; } /* * Some of the interrupts that are generated when these events occur * may be disabled due to module parameters. So we make sure to enable * them here, depending on which event was requested. If some events * were requested before and now this function is called again with * other events, though, we want to restore the interrupt configuration * to the default state according to the module parameters. */ reg = readl_relaxed(device->base + TSPP2_GLOBAL_IRQ_ENABLE); if (global_event_bitmask & TSPP2_GLOBAL_EVENT_INVALID_AF_CTRL) { reg |= (0x1 << GLOBAL_IRQ_TSP_INVALID_AF_OFFS); } else { if (tspp2_en_invalid_af_ctrl) reg |= (0x1 << GLOBAL_IRQ_TSP_INVALID_AF_OFFS); else reg &= ~(0x1 << GLOBAL_IRQ_TSP_INVALID_AF_OFFS); } if (global_event_bitmask & TSPP2_GLOBAL_EVENT_INVALID_AF_LENGTH) { reg |= (0x1 << GLOBAL_IRQ_TSP_INVALID_LEN_OFFS); } else { if (tspp2_en_invalid_af_length) reg |= (0x1 << GLOBAL_IRQ_TSP_INVALID_LEN_OFFS); else reg &= ~(0x1 << GLOBAL_IRQ_TSP_INVALID_LEN_OFFS); } if (global_event_bitmask & TSPP2_GLOBAL_EVENT_PES_NO_SYNC) { reg |= (0x1 << GLOBAL_IRQ_PES_NO_SYNC_OFFS); } else { if (tspp2_en_pes_no_sync) reg |= (0x1 << GLOBAL_IRQ_PES_NO_SYNC_OFFS); else reg &= ~(0x1 << GLOBAL_IRQ_PES_NO_SYNC_OFFS); } writel_relaxed(reg, device->base + TSPP2_GLOBAL_IRQ_ENABLE); spin_lock_irqsave(&device->spinlock, flags); device->event_callback = callback; device->event_cookie = cookie; device->event_bitmask = global_event_bitmask; spin_unlock_irqrestore(&device->spinlock, flags); mutex_unlock(&device->mutex); dev_dbg(device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_global_event_notification_register); /** * tspp2_src_event_notification_register() - Get notified on a source event. * * @src_handle: Source handle. * @src_event_bitmask: A bitmask of source events, * TSPP2_SRC_EVENT_XXX. * @callback: User callback function. * @cookie: User information passed to the callback. * * Register a user callback which will be invoked when certain source * events occur. Note the values (mask, callback and cookie) are overwritten * when calling this function multiple times. Therefore it is possible to * "unregister" a callback by calling this function with the bitmask set to 0 * and with NULL callback and cookie. * * Return 0 on success, error value otherwise. */ int tspp2_src_event_notification_register(u32 src_handle, u32 src_event_bitmask, void (*callback)(void *cookie, u32 event_bitmask), void *cookie) { int ret; u32 reg; unsigned long flags; struct tspp2_src *src = (struct tspp2_src *)src_handle; if (!src) { pr_err("%s: Invalid source handle\n", __func__); return -EINVAL; } ret = pm_runtime_get_sync(src->device->dev); if (ret < 0) return ret; if (mutex_lock_interruptible(&src->device->mutex)) { pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -ERESTARTSYS; } if (!src->device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EPERM; } if (!src->opened) { pr_err("%s: Source not opened\n", __func__); goto err_inval; } if (((src->input == TSPP2_INPUT_TSIF0) || (src->input == TSPP2_INPUT_TSIF1)) && ((src_event_bitmask & TSPP2_SRC_EVENT_MEMORY_READ_ERROR) || (src_event_bitmask & TSPP2_SRC_EVENT_FLOW_CTRL_STALL))) { pr_err("%s: Invalid event bitmask for a source with TSIF input\n", __func__); goto err_inval; } if ((src->input == TSPP2_INPUT_MEMORY) && ((src_event_bitmask & TSPP2_SRC_EVENT_TSIF_LOST_SYNC) || (src_event_bitmask & TSPP2_SRC_EVENT_TSIF_TIMEOUT) || (src_event_bitmask & TSPP2_SRC_EVENT_TSIF_OVERFLOW) || (src_event_bitmask & TSPP2_SRC_EVENT_TSIF_PKT_READ_ERROR) || (src_event_bitmask & TSPP2_SRC_EVENT_TSIF_PKT_WRITE_ERROR))) { pr_err("%s: Invalid event bitmask for a source with memory input\n", __func__); goto err_inval; } spin_lock_irqsave(&src->device->spinlock, flags); src->event_callback = callback; src->event_cookie = cookie; src->event_bitmask = src_event_bitmask; spin_unlock_irqrestore(&src->device->spinlock, flags); /* Enable/disable flow control stall interrupt on the source */ reg = readl_relaxed(src->device->base + TSPP2_GLOBAL_IRQ_ENABLE); if (callback && (src_event_bitmask & TSPP2_SRC_EVENT_FLOW_CTRL_STALL)) { reg |= ((0x1 << src->hw_index) << GLOBAL_IRQ_FC_STALL_OFFS); } else { reg &= ~((0x1 << src->hw_index) << GLOBAL_IRQ_FC_STALL_OFFS); } writel_relaxed(reg, src->device->base + TSPP2_GLOBAL_IRQ_ENABLE); mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); dev_dbg(src->device->dev, "%s: successful\n", __func__); return 0; err_inval: mutex_unlock(&src->device->mutex); pm_runtime_mark_last_busy(src->device->dev); pm_runtime_put_autosuspend(src->device->dev); return -EINVAL; } EXPORT_SYMBOL(tspp2_src_event_notification_register); /** * tspp2_filter_event_notification_register() - Get notified on a filter event. * * @filter_handle: Filter handle. * @filter_event_bitmask: A bitmask of filter events, * TSPP2_FILTER_EVENT_XXX. * @callback: User callback function. * @cookie: User information passed to the callback. * * Register a user callback which will be invoked when certain filter * events occur. Note the values (mask, callback and cookie) are overwritten * when calling this function multiple times. Therefore it is possible to * "unregister" a callback by calling this function with the bitmask set to 0 * and with NULL callback and cookie. * * Return 0 on success, error value otherwise. */ int tspp2_filter_event_notification_register(u32 filter_handle, u32 filter_event_bitmask, void (*callback)(void *cookie, u32 event_bitmask), void *cookie) { int ret; int idx; u32 reg; unsigned long flags; struct tspp2_filter *filter = (struct tspp2_filter *)filter_handle; if (!filter) { pr_err("%s: Invalid filter handle\n", __func__); return -EINVAL; } ret = pm_runtime_get_sync(filter->device->dev); if (ret < 0) return ret; if (mutex_lock_interruptible(&filter->device->mutex)) { pm_runtime_mark_last_busy(filter->device->dev); pm_runtime_put_autosuspend(filter->device->dev); return -ERESTARTSYS; } if (!filter->device->opened) { pr_err("%s: Device must be opened first\n", __func__); mutex_unlock(&filter->device->mutex); pm_runtime_mark_last_busy(filter->device->dev); pm_runtime_put_autosuspend(filter->device->dev); return -EPERM; } if (!filter->opened) { pr_err("%s: Filter not opened\n", __func__); mutex_unlock(&filter->device->mutex); pm_runtime_mark_last_busy(filter->device->dev); pm_runtime_put_autosuspend(filter->device->dev); return -EINVAL; } spin_lock_irqsave(&filter->device->spinlock, flags); filter->event_callback = callback; filter->event_cookie = cookie; filter->event_bitmask = filter_event_bitmask; spin_unlock_irqrestore(&filter->device->spinlock, flags); /* Enable/disable SC high/low interrupts per filter as requested */ idx = (filter->context >> 5); reg = readl_relaxed(filter->device->base + TSPP2_SC_GO_HIGH_ENABLE(idx)); if (callback && (filter_event_bitmask & TSPP2_FILTER_EVENT_SCRAMBLING_HIGH)) { reg |= (0x1 << TSPP2_MODULUS_OP(filter->context, 32)); } else { reg &= ~(0x1 << TSPP2_MODULUS_OP(filter->context, 32)); } writel_relaxed(reg, filter->device->base + TSPP2_SC_GO_HIGH_ENABLE(idx)); reg = readl_relaxed(filter->device->base + TSPP2_SC_GO_LOW_ENABLE(idx)); if (callback && (filter_event_bitmask & TSPP2_FILTER_EVENT_SCRAMBLING_LOW)) { reg |= (0x1 << TSPP2_MODULUS_OP(filter->context, 32)); } else { reg &= ~(0x1 << TSPP2_MODULUS_OP(filter->context, 32)); } writel_relaxed(reg, filter->device->base + TSPP2_SC_GO_LOW_ENABLE(idx)); mutex_unlock(&filter->device->mutex); pm_runtime_mark_last_busy(filter->device->dev); pm_runtime_put_autosuspend(filter->device->dev); dev_dbg(filter->device->dev, "%s: successful\n", __func__); return 0; } EXPORT_SYMBOL(tspp2_filter_event_notification_register); /** * tspp2_get_filter_hw_index() - Get a filter's hardware index. * * @filter_handle: Filter handle. * * This is an helper function to support tspp2 auto-testing. * * Return the filter's hardware index on success, error value otherwise. */ int tspp2_get_filter_hw_index(u32 filter_handle) { struct tspp2_filter *filter = (struct tspp2_filter *)filter_handle; if (!filter_handle) return -EINVAL; return filter->hw_index; } EXPORT_SYMBOL(tspp2_get_filter_hw_index); /** * tspp2_get_reserved_hw_index() - Get a source's reserved hardware index. * * @src_handle: Source handle. * * This is an helper function to support tspp2 auto-testing. * * Return the source's reserved hardware index on success, * error value otherwise. */ int tspp2_get_reserved_hw_index(u32 src_handle) { struct tspp2_src *src = (struct tspp2_src *)src_handle; if (!src_handle) return -EINVAL; return src->reserved_filter_hw_index; } EXPORT_SYMBOL(tspp2_get_reserved_hw_index); /** * tspp2_get_ops_array() - Get filter's operations. * * @filter_handle: Filter handle. * @ops_array: The filter's operations. * @num_of_ops: The filter's number of operations. * * This is an helper function to support tspp2 auto-testing. * * Return 0 on success, error value otherwise. */ int tspp2_get_ops_array(u32 filter_handle, struct tspp2_operation ops_array[TSPP2_MAX_OPS_PER_FILTER], u8 *num_of_ops) { int i; struct tspp2_filter *filter = (struct tspp2_filter *)filter_handle; if (!filter_handle || !num_of_ops) return -EINVAL; *num_of_ops = filter->num_user_operations; for (i = 0; i < *num_of_ops; i++) ops_array[i] = filter->operations[i]; return 0; } EXPORT_SYMBOL(tspp2_get_ops_array); /* Platform driver related functions: */ /** * msm_tspp2_dt_to_pdata() - Copy device-tree data to platfrom data structure. * * @pdev: Platform device. * * Return pointer to allocated platform data on success, NULL on failure. */ static struct msm_tspp2_platform_data * msm_tspp2_dt_to_pdata(struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node; struct msm_tspp2_platform_data *data; int rc; /* Note: memory allocated by devm_kzalloc is freed automatically */ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); if (!data) { pr_err("%s: Unable to allocate platform data\n", __func__); return NULL; } /* Get power regulator */ if (!of_get_property(node, "vdd-supply", NULL)) { pr_err("%s: Could not find vdd-supply property\n", __func__); return NULL; } /* Get IOMMU information */ rc = of_property_read_string(node, "qcom,iommu-hlos-group", &data->hlos_group); if (rc) { pr_err("%s: Could not find iommu-hlos-group property, err = %d\n", __func__, rc); return NULL; } rc = of_property_read_string(node, "qcom,iommu-cpz-group", &data->cpz_group); if (rc) { pr_err("%s: Could not find iommu-cpz-group property, err = %d\n", __func__, rc); return NULL; } rc = of_property_read_u32(node, "qcom,iommu-hlos-partition", &data->hlos_partition); if (rc) { pr_err("%s: Could not find iommu-hlos-partition property, err = %d\n", __func__, rc); return NULL; } rc = of_property_read_u32(node, "qcom,iommu-cpz-partition", &data->cpz_partition); if (rc) { pr_err("%s: Could not find iommu-cpz-partition property, err = %d\n", __func__, rc); return NULL; } return data; } static void msm_tspp2_iommu_info_free(struct tspp2_device *device) { if (device->iommu_info.hlos_group) { iommu_group_put(device->iommu_info.hlos_group); device->iommu_info.hlos_group = NULL; } if (device->iommu_info.cpz_group) { iommu_group_put(device->iommu_info.cpz_group); device->iommu_info.cpz_group = NULL; } device->iommu_info.hlos_domain = NULL; device->iommu_info.cpz_domain = NULL; device->iommu_info.hlos_domain_num = -1; device->iommu_info.cpz_domain_num = -1; device->iommu_info.hlos_partition = -1; device->iommu_info.cpz_partition = -1; } /** * msm_tspp2_iommu_info_get() - Get IOMMU information. * * @pdev: Platform device, containing platform information. * @device: TSPP2 device. * * Return 0 on success, error value otherwise. */ static int msm_tspp2_iommu_info_get(struct platform_device *pdev, struct tspp2_device *device) { int ret = 0; struct msm_tspp2_platform_data *data = pdev->dev.platform_data; device->iommu_info.hlos_group = NULL; device->iommu_info.cpz_group = NULL; device->iommu_info.hlos_domain = NULL; device->iommu_info.cpz_domain = NULL; device->iommu_info.hlos_domain_num = -1; device->iommu_info.cpz_domain_num = -1; device->iommu_info.hlos_partition = -1; device->iommu_info.cpz_partition = -1; device->iommu_info.hlos_group = iommu_group_find(data->hlos_group); if (!device->iommu_info.hlos_group) { dev_err(&pdev->dev, "%s: Cannot find IOMMU HLOS group", __func__); ret = -EINVAL; goto err_out; } device->iommu_info.cpz_group = iommu_group_find(data->cpz_group); if (!device->iommu_info.cpz_group) { dev_err(&pdev->dev, "%s: Cannot find IOMMU CPZ group", __func__); ret = -EINVAL; goto err_out; } device->iommu_info.hlos_domain = iommu_group_get_iommudata(device->iommu_info.hlos_group); if (IS_ERR_OR_NULL(device->iommu_info.hlos_domain)) { dev_err(&pdev->dev, "%s: iommu_group_get_iommudata failed", __func__); ret = -EINVAL; goto err_out; } device->iommu_info.cpz_domain = iommu_group_get_iommudata(device->iommu_info.cpz_group); if (IS_ERR_OR_NULL(device->iommu_info.cpz_domain)) { device->iommu_info.hlos_domain = NULL; dev_err(&pdev->dev, "%s: iommu_group_get_iommudata failed", __func__); ret = -EINVAL; goto err_out; } device->iommu_info.hlos_domain_num = msm_find_domain_no(device->iommu_info.hlos_domain); device->iommu_info.cpz_domain_num = msm_find_domain_no(device->iommu_info.cpz_domain); device->iommu_info.hlos_partition = data->hlos_partition; device->iommu_info.cpz_partition = data->cpz_partition; return 0; err_out: msm_tspp2_iommu_info_free(device); return ret; } /** * tspp2_clocks_put() - Put clocks and disable regulator. * * @device: TSPP2 device. */ static void tspp2_clocks_put(struct tspp2_device *device) { if (device->tsif_ref_clk) clk_put(device->tsif_ref_clk); if (device->tspp2_klm_ahb_clk) clk_put(device->tspp2_klm_ahb_clk); if (device->tspp2_vbif_clk) clk_put(device->tspp2_vbif_clk); if (device->vbif_ahb_clk) clk_put(device->vbif_ahb_clk); if (device->vbif_axi_clk) clk_put(device->vbif_axi_clk); if (device->tspp2_core_clk) clk_put(device->tspp2_core_clk); if (device->tspp2_ahb_clk) clk_put(device->tspp2_ahb_clk); device->tspp2_ahb_clk = NULL; device->tspp2_core_clk = NULL; device->tspp2_vbif_clk = NULL; device->vbif_ahb_clk = NULL; device->vbif_axi_clk = NULL; device->tspp2_klm_ahb_clk = NULL; device->tsif_ref_clk = NULL; } /** * msm_tspp2_clocks_setup() - Get clocks and set their rate, enable regulator. * * @pdev: Platform device, containing platform information. * @device: TSPP2 device. * * Return 0 on success, error value otherwise. */ static int msm_tspp2_clocks_setup(struct platform_device *pdev, struct tspp2_device *device) { int ret = 0; unsigned long rate_in_hz = 0; struct clk *tspp2_core_clk_src = NULL; /* Get power regulator (GDSC) */ device->gdsc = devm_regulator_get(&pdev->dev, "vdd"); if (IS_ERR(device->gdsc)) { pr_err("%s: Failed to get vdd power regulator\n", __func__); ret = PTR_ERR(device->gdsc); device->gdsc = NULL; return ret; } device->tspp2_ahb_clk = NULL; device->tspp2_core_clk = NULL; device->tspp2_vbif_clk = NULL; device->vbif_ahb_clk = NULL; device->vbif_axi_clk = NULL; device->tspp2_klm_ahb_clk = NULL; device->tsif_ref_clk = NULL; device->tspp2_ahb_clk = clk_get(&pdev->dev, "bcc_tspp2_ahb_clk"); if (IS_ERR(device->tspp2_ahb_clk)) { pr_err("%s: Failed to get %s", __func__, "bcc_tspp2_ahb_clk"); ret = PTR_ERR(device->tspp2_ahb_clk); device->tspp2_ahb_clk = NULL; goto err_clocks; } device->tspp2_core_clk = clk_get(&pdev->dev, "bcc_tspp2_core_clk"); if (IS_ERR(device->tspp2_core_clk)) { pr_err("%s: Failed to get %s", __func__, "bcc_tspp2_core_clk"); ret = PTR_ERR(device->tspp2_core_clk); device->tspp2_core_clk = NULL; goto err_clocks; } device->tspp2_vbif_clk = clk_get(&pdev->dev, "bcc_vbif_tspp2_clk"); if (IS_ERR(device->tspp2_vbif_clk)) { pr_err("%s: Failed to get %s", __func__, "bcc_vbif_tspp2_clk"); ret = PTR_ERR(device->tspp2_vbif_clk); device->tspp2_vbif_clk = NULL; goto err_clocks; } device->vbif_ahb_clk = clk_get(&pdev->dev, "iface_vbif_clk"); if (IS_ERR(device->vbif_ahb_clk)) { pr_err("%s: Failed to get %s", __func__, "iface_vbif_clk"); ret = PTR_ERR(device->vbif_ahb_clk); device->vbif_ahb_clk = NULL; goto err_clocks; } device->vbif_axi_clk = clk_get(&pdev->dev, "vbif_core_clk"); if (IS_ERR(device->vbif_axi_clk)) { pr_err("%s: Failed to get %s", __func__, "vbif_core_clk"); ret = PTR_ERR(device->vbif_axi_clk); device->vbif_axi_clk = NULL; goto err_clocks; } device->tspp2_klm_ahb_clk = clk_get(&pdev->dev, "bcc_klm_ahb_clk"); if (IS_ERR(device->tspp2_klm_ahb_clk)) { pr_err("%s: Failed to get %s", __func__, "bcc_klm_ahb_clk"); ret = PTR_ERR(device->tspp2_klm_ahb_clk); device->tspp2_klm_ahb_clk = NULL; goto err_clocks; } device->tsif_ref_clk = clk_get(&pdev->dev, "gcc_tsif_ref_clk"); if (IS_ERR(device->tsif_ref_clk)) { pr_err("%s: Failed to get %s", __func__, "gcc_tsif_ref_clk"); ret = PTR_ERR(device->tsif_ref_clk); device->tsif_ref_clk = NULL; goto err_clocks; } /* Set relevant clock rates */ rate_in_hz = clk_round_rate(device->tsif_ref_clk, 1); if (clk_set_rate(device->tsif_ref_clk, rate_in_hz)) { pr_err("%s: Failed to set rate %lu to %s\n", __func__, rate_in_hz, "gcc_tsif_ref_clk"); goto err_clocks; } /* We need to set the rate of tspp2_core_clk_src */ tspp2_core_clk_src = clk_get_parent(device->tspp2_core_clk); if (tspp2_core_clk_src) { rate_in_hz = clk_round_rate(tspp2_core_clk_src, 1); if (clk_set_rate(tspp2_core_clk_src, rate_in_hz)) { pr_err("%s: Failed to set rate %lu to tspp2_core_clk_src\n", __func__, rate_in_hz); goto err_clocks; } } else { pr_err("%s: Failed to get tspp2_core_clk parent\n", __func__); goto err_clocks; } return 0; err_clocks: tspp2_clocks_put(device); return ret; } /** * msm_tspp2_map_io_memory() - Map memory resources to kernel space. * * @pdev: Platform device, containing platform information. * @device: TSPP2 device. * * Return 0 on success, error value otherwise. */ static int msm_tspp2_map_io_memory(struct platform_device *pdev, struct tspp2_device *device) { struct resource *mem_tsif0; struct resource *mem_tsif1; struct resource *mem_tspp2; struct resource *mem_bam; /* Get memory resources */ mem_tsif0 = platform_get_resource_byname(pdev, IORESOURCE_MEM, "MSM_TSIF0"); if (!mem_tsif0) { dev_err(&pdev->dev, "%s: Missing TSIF0 MEM resource", __func__); return -ENXIO; } mem_tsif1 = platform_get_resource_byname(pdev, IORESOURCE_MEM, "MSM_TSIF1"); if (!mem_tsif1) { dev_err(&pdev->dev, "%s: Missing TSIF1 MEM resource", __func__); return -ENXIO; } mem_tspp2 = platform_get_resource_byname(pdev, IORESOURCE_MEM, "MSM_TSPP2"); if (!mem_tspp2) { dev_err(&pdev->dev, "%s: Missing TSPP2 MEM resource", __func__); return -ENXIO; } mem_bam = platform_get_resource_byname(pdev, IORESOURCE_MEM, "MSM_TSPP2_BAM"); if (!mem_bam) { dev_err(&pdev->dev, "%s: Missing BAM MEM resource", __func__); return -ENXIO; } /* Map memory physical addresses to kernel space */ device->tsif_devices[0].base = ioremap(mem_tsif0->start, resource_size(mem_tsif0)); if (!device->tsif_devices[0].base) { dev_err(&pdev->dev, "%s: ioremap failed", __func__); goto err_map_tsif0; } device->tsif_devices[1].base = ioremap(mem_tsif1->start, resource_size(mem_tsif1)); if (!device->tsif_devices[1].base) { dev_err(&pdev->dev, "%s: ioremap failed", __func__); goto err_map_tsif1; } device->base = ioremap(mem_tspp2->start, resource_size(mem_tspp2)); if (!device->base) { dev_err(&pdev->dev, "%s: ioremap failed", __func__); goto err_map_dev; } memset(&device->bam_props, 0, sizeof(device->bam_props)); device->bam_props.phys_addr = mem_bam->start; device->bam_props.virt_addr = ioremap(mem_bam->start, resource_size(mem_bam)); if (!device->bam_props.virt_addr) { dev_err(&pdev->dev, "%s: ioremap failed", __func__); goto err_map_bam; } return 0; err_map_bam: iounmap(device->base); err_map_dev: iounmap(device->tsif_devices[1].base); err_map_tsif1: iounmap(device->tsif_devices[0].base); err_map_tsif0: return -ENXIO; } /** * tspp2_event_work_prepare() - Prepare and queue a work element. * * @device: TSPP2 device. * @callback: User callback to invoke. * @cookie: User cookie. * @event_bitmask: Event bitmask * * Get a free work element from the pool, prepare it and queue it * to the work queue. When scheduled, the work will invoke the user callback * for the event that the HW reported. */ static void tspp2_event_work_prepare(struct tspp2_device *device, void (*callback)(void *cookie, u32 event_bitmask), void *cookie, u32 event_bitmask) { struct tspp2_event_work *work = NULL; if (!list_empty(&device->free_work_list)) { work = list_first_entry(&device->free_work_list, struct tspp2_event_work, link); list_del(&work->link); work->callback = callback; work->cookie = cookie; work->event_bitmask = event_bitmask; queue_work(device->work_queue, &work->work); } else { pr_warn("%s: No available work element\n", __func__); } } /** * tspp2_isr() - TSPP2 interrupt handler. * * @irq: Interrupt number. * @dev: TSPP2 device. * * Handle TSPP2 HW interrupt. Collect relevant statistics and invoke * user registered callbacks for global, source or filter events. * * Return IRQ_HANDLED. */ static irqreturn_t tspp2_isr(int irq, void *dev) { struct tspp2_device *device = dev; struct tspp2_src *src = NULL; struct tspp2_filter *f = NULL; unsigned long ext_reg = 0; unsigned long val = 0; unsigned long flags; u32 i = 0, j = 0; u32 global_bitmask = 0; u32 src_bitmask[TSPP2_NUM_MEM_INPUTS] = {0}; u32 filter_bitmask[TSPP2_NUM_CONTEXTS] = {0}; u32 reg = 0; reg = readl_relaxed(device->base + TSPP2_GLOBAL_IRQ_STATUS); if (reg & (0x1 << GLOBAL_IRQ_TSP_INVALID_AF_OFFS)) { device->irq_stats.global.tsp_invalid_af_control++; global_bitmask |= TSPP2_GLOBAL_EVENT_INVALID_AF_CTRL; } if (reg & (0x1 << GLOBAL_IRQ_TSP_INVALID_LEN_OFFS)) { device->irq_stats.global.tsp_invalid_length++; global_bitmask |= TSPP2_GLOBAL_EVENT_INVALID_AF_LENGTH; } if (reg & (0x1 << GLOBAL_IRQ_PES_NO_SYNC_OFFS)) { device->irq_stats.global.pes_no_sync++; global_bitmask |= TSPP2_GLOBAL_EVENT_PES_NO_SYNC; } if (reg & (0x1 << GLOBAL_IRQ_ENCRYPT_LEVEL_ERR_OFFS)) device->irq_stats.global.encrypt_level_err++; if (reg & (0x1 << GLOBAL_IRQ_KEY_NOT_READY_OFFS)) { ext_reg = readl_relaxed(device->base + TSPP2_KEY_NOT_READY_IRQ_STATUS); for_each_set_bit(i, &ext_reg, TSPP2_NUM_KEYTABLES) device->irq_stats.kt[i].key_not_ready++; writel_relaxed(ext_reg, device->base + TSPP2_KEY_NOT_READY_IRQ_CLEAR); } if (reg & (0x1 << GLOBAL_IRQ_UNEXPECTED_RESET_OFFS)) { ext_reg = readl_relaxed(device->base + TSPP2_UNEXPECTED_RST_IRQ_STATUS); for_each_set_bit(i, &ext_reg, TSPP2_NUM_PIPES) device->irq_stats.pipe[i].unexpected_reset++; writel_relaxed(ext_reg, device->base + TSPP2_UNEXPECTED_RST_IRQ_CLEAR); } if (reg & (0x1 << GLOBAL_IRQ_WRONG_PIPE_DIR_OFFS)) { ext_reg = readl_relaxed(device->base + TSPP2_WRONG_PIPE_DIR_IRQ_STATUS); for_each_set_bit(i, &ext_reg, TSPP2_NUM_PIPES) device->irq_stats.pipe[i].wrong_pipe_direction++; writel_relaxed(ext_reg, device->base + TSPP2_WRONG_PIPE_DIR_IRQ_CLEAR); } if (reg & (0x1 << GLOBAL_IRQ_QSB_RESP_ERR_OFFS)) { global_bitmask |= TSPP2_GLOBAL_EVENT_TX_FAIL; ext_reg = readl_relaxed(device->base + TSPP2_QSB_RESPONSE_ERROR_IRQ_STATUS); for_each_set_bit(i, &ext_reg, TSPP2_NUM_PIPES) device->irq_stats.pipe[i].qsb_response_error++; writel_relaxed(ext_reg, device->base + TSPP2_QSB_RESPONSE_ERROR_IRQ_CLEAR); } if (reg & (0x1 << GLOBAL_IRQ_SC_GO_HIGH_OFFS)) { for (j = 0; j < 3; j++) { ext_reg = readl_relaxed(device->base + TSPP2_SC_GO_HIGH_STATUS(j)); for_each_set_bit(i, &ext_reg, 32) { filter_bitmask[j*32 + i] |= TSPP2_FILTER_EVENT_SCRAMBLING_HIGH; device->irq_stats.ctx[j*32 + i].sc_go_high++; } writel_relaxed(ext_reg, device->base + TSPP2_SC_GO_HIGH_CLEAR(j)); } } if (reg & (0x1 << GLOBAL_IRQ_SC_GO_LOW_OFFS)) { for (j = 0; j < 3; j++) { ext_reg = readl_relaxed(device->base + TSPP2_SC_GO_LOW_STATUS(j)); for_each_set_bit(i, &ext_reg, 32) { filter_bitmask[j*32 + i] |= TSPP2_FILTER_EVENT_SCRAMBLING_LOW; device->irq_stats.ctx[j*32 + i].sc_go_low++; } writel_relaxed(ext_reg, device->base + TSPP2_SC_GO_LOW_CLEAR(j)); } } if (reg & (0xFF << GLOBAL_IRQ_READ_FAIL_OFFS)) { val = ((reg & (0xFF << GLOBAL_IRQ_READ_FAIL_OFFS)) >> GLOBAL_IRQ_READ_FAIL_OFFS); for_each_set_bit(i, &val, TSPP2_NUM_MEM_INPUTS) { src_bitmask[i] |= TSPP2_SRC_EVENT_MEMORY_READ_ERROR; device->irq_stats.src[i].read_failure++; } } if (reg & (0xFF << GLOBAL_IRQ_FC_STALL_OFFS)) { val = ((reg & (0xFF << GLOBAL_IRQ_FC_STALL_OFFS)) >> GLOBAL_IRQ_FC_STALL_OFFS); for_each_set_bit(i, &val, TSPP2_NUM_MEM_INPUTS) { src_bitmask[i] |= TSPP2_SRC_EVENT_FLOW_CTRL_STALL; device->irq_stats.src[i].flow_control_stall++; } } spin_lock_irqsave(&device->spinlock, flags); /* Invoke user callback for global events */ if (device->event_callback && (global_bitmask & device->event_bitmask)) tspp2_event_work_prepare(device, device->event_callback, device->event_cookie, (global_bitmask & device->event_bitmask)); /* Invoke user callbacks on memory source events */ for (i = 0; i < TSPP2_NUM_MEM_INPUTS; i++) { src = &device->mem_sources[i]; if (src->event_callback && (src_bitmask[src->hw_index] & src->event_bitmask)) tspp2_event_work_prepare(device, src->event_callback, src->event_cookie, (src_bitmask[src->hw_index] & src->event_bitmask)); } /* Invoke user callbacks on filter events */ for (i = 0; i < TSPP2_NUM_AVAIL_FILTERS; i++) { f = &device->filters[i]; if (f->event_callback && (f->event_bitmask & filter_bitmask[f->context])) tspp2_event_work_prepare(device, f->event_callback, f->event_cookie, (f->event_bitmask & filter_bitmask[f->context])); } spin_unlock_irqrestore(&device->spinlock, flags); /* * Clear global interrupts. Note bits [9:4] are an aggregation of * other IRQs, and are reserved in the TSPP2_GLOBAL_IRQ_CLEAR register. */ reg &= ~(0x0FFF << GLOBAL_IRQ_CLEAR_RESERVED_OFFS); writel_relaxed(reg, device->base + TSPP2_GLOBAL_IRQ_CLEAR); /* * Before returning IRQ_HANDLED to the generic interrupt handling * framework, we need to make sure all operations, including clearing of * interrupt status registers in the hardware, are performed. * Thus a barrier after clearing the interrupt status register * is required to guarantee that the interrupt status register has * really been cleared by the time we return from this handler. */ wmb(); return IRQ_HANDLED; } /** * tsif_isr() - TSIF interrupt handler. * * @irq: Interrupt number. * @dev: TSIF device that generated the interrupt. * * Handle TSIF HW interrupt. Collect HW statistics and, if the user registered * a relevant source callback, invoke it. * * Return IRQ_HANDLED on success, IRQ_NONE on irrelevant interrupts. */ static irqreturn_t tsif_isr(int irq, void *dev) { u32 src_bitmask = 0; unsigned long flags; struct tspp2_src *src = NULL; struct tspp2_tsif_device *tsif_device = dev; u32 sts_ctl = 0; sts_ctl = readl_relaxed(tsif_device->base + TSPP2_TSIF_STS_CTL); if (!(sts_ctl & (TSIF_STS_CTL_PACK_AVAIL | TSIF_STS_CTL_PKT_WRITE_ERR | TSIF_STS_CTL_PKT_READ_ERR | TSIF_STS_CTL_OVERFLOW | TSIF_STS_CTL_LOST_SYNC | TSIF_STS_CTL_TIMEOUT))) { return IRQ_NONE; } if (sts_ctl & TSIF_STS_CTL_PKT_WRITE_ERR) { src_bitmask |= TSPP2_SRC_EVENT_TSIF_PKT_WRITE_ERROR; tsif_device->stat_pkt_write_err++; } if (sts_ctl & TSIF_STS_CTL_PKT_READ_ERR) { src_bitmask |= TSPP2_SRC_EVENT_TSIF_PKT_READ_ERROR; tsif_device->stat_pkt_read_err++; } if (sts_ctl & TSIF_STS_CTL_OVERFLOW) { src_bitmask |= TSPP2_SRC_EVENT_TSIF_OVERFLOW; tsif_device->stat_overflow++; } if (sts_ctl & TSIF_STS_CTL_LOST_SYNC) { src_bitmask |= TSPP2_SRC_EVENT_TSIF_LOST_SYNC; tsif_device->stat_lost_sync++; } if (sts_ctl & TSIF_STS_CTL_TIMEOUT) { src_bitmask |= TSPP2_SRC_EVENT_TSIF_TIMEOUT; tsif_device->stat_timeout++; } /* Invoke user TSIF source callbacks if registered for these events */ src = &tsif_device->dev->tsif_sources[tsif_device->hw_index]; spin_lock_irqsave(&src->device->spinlock, flags); if (src->event_callback && (src->event_bitmask & src_bitmask)) tspp2_event_work_prepare(tsif_device->dev, src->event_callback, src->event_cookie, (src->event_bitmask & src_bitmask)); spin_unlock_irqrestore(&src->device->spinlock, flags); writel_relaxed(sts_ctl, tsif_device->base + TSPP2_TSIF_STS_CTL); /* * Before returning IRQ_HANDLED to the generic interrupt handling * framework, we need to make sure all operations, including clearing of * interrupt status registers in the hardware, are performed. * Thus a barrier after clearing the interrupt status register * is required to guarantee that the interrupt status register has * really been cleared by the time we return from this handler. */ wmb(); return IRQ_HANDLED; } /** * msm_tspp2_map_irqs() - Get and request IRQs. * * @pdev: Platform device, containing platform information. * @device: TSPP2 device. * * Helper function to get IRQ numbers from the platform device and request * the IRQs (i.e., set interrupt handlers) for the TSPP2 and TSIF interrupts. * * Return 0 on success, error value otherwise. */ static int msm_tspp2_map_irqs(struct platform_device *pdev, struct tspp2_device *device) { int rc; int i; /* get IRQ numbers from platform information */ rc = platform_get_irq_byname(pdev, "TSPP2"); if (rc > 0) { device->tspp2_irq = rc; } else { dev_err(&pdev->dev, "%s: Failed to get TSPP2 IRQ", __func__); return -EINVAL; } rc = platform_get_irq_byname(pdev, "TSIF0"); if (rc > 0) { device->tsif_devices[0].tsif_irq = rc; } else { dev_err(&pdev->dev, "%s: Failed to get TSIF0 IRQ", __func__); return -EINVAL; } rc = platform_get_irq_byname(pdev, "TSIF1"); if (rc > 0) { device->tsif_devices[1].tsif_irq = rc; } else { dev_err(&pdev->dev, "%s: Failed to get TSIF1 IRQ", __func__); return -EINVAL; } rc = platform_get_irq_byname(pdev, "TSPP2_BAM"); if (rc > 0) { device->bam_irq = rc; } else { dev_err(&pdev->dev, "%s: Failed to get TSPP2 BAM IRQ", __func__); return -EINVAL; } rc = request_irq(device->tspp2_irq, tspp2_isr, IRQF_SHARED, dev_name(&pdev->dev), device); if (rc) { dev_err(&pdev->dev, "%s: Failed to request TSPP2 IRQ %d : %d", __func__, device->tspp2_irq, rc); goto request_irq_err; } for (i = 0; i < TSPP2_NUM_TSIF_INPUTS; i++) { rc = request_irq(device->tsif_devices[i].tsif_irq, tsif_isr, IRQF_SHARED, dev_name(&pdev->dev), &device->tsif_devices[i]); if (rc) { dev_warn(&pdev->dev, "%s: Failed to request TSIF%d IRQ: %d", __func__, i, rc); device->tsif_devices[i].tsif_irq = 0; } } return 0; request_irq_err: device->tspp2_irq = 0; device->tsif_devices[0].tsif_irq = 0; device->tsif_devices[1].tsif_irq = 0; device->bam_irq = 0; return -EINVAL; } /* Device driver probe function */ static int msm_tspp2_probe(struct platform_device *pdev) { int rc = 0; struct msm_tspp2_platform_data *data; struct tspp2_device *device; struct msm_bus_scale_pdata *tspp2_bus_pdata = NULL; if (pdev->dev.of_node) { /* Get information from device tree */ data = msm_tspp2_dt_to_pdata(pdev); /* get device ID */ rc = of_property_read_u32(pdev->dev.of_node, "cell-index", &pdev->id); if (rc) pdev->id = -1; tspp2_bus_pdata = msm_bus_cl_get_pdata(pdev); pdev->dev.platform_data = data; } else { /* Get information from platform data */ data = pdev->dev.platform_data; } if (!data) { pr_err("%s: Platform data not available\n", __func__); return -EINVAL; } /* Verify device id is valid */ if ((pdev->id < 0) || (pdev->id >= TSPP2_NUM_DEVICES)) { pr_err("%s: Invalid device ID %d\n", __func__, pdev->id); return -EINVAL; } device = devm_kzalloc(&pdev->dev, sizeof(struct tspp2_device), GFP_KERNEL); if (!device) { pr_err("%s: Failed to allocate memory for device\n", __func__); return -ENOMEM; } platform_set_drvdata(pdev, device); device->pdev = pdev; device->dev = &pdev->dev; device->dev_id = pdev->id; device->opened = 0; /* Register bus client */ if (tspp2_bus_pdata) { device->bus_client = msm_bus_scale_register_client(tspp2_bus_pdata); if (!device->bus_client) pr_err("%s: Unable to register bus client\n", __func__); } else { pr_err("%s: Platform bus client data not available. Continue anyway...\n", __func__); } rc = msm_tspp2_iommu_info_get(pdev, device); if (rc) { pr_err("%s: Failed to get IOMMU information\n", __func__); goto err_bus_client; } rc = msm_tspp2_clocks_setup(pdev, device); if (rc) goto err_clocks_setup; rc = msm_tspp2_map_io_memory(pdev, device); if (rc) goto err_map_io_memory; rc = msm_tspp2_map_irqs(pdev, device); if (rc) goto err_map_irq; mutex_init(&device->mutex); tspp2_devices[pdev->id] = device; tspp2_debugfs_init(device); return rc; err_map_irq: iounmap(device->base); iounmap(device->tsif_devices[0].base); iounmap(device->tsif_devices[1].base); iounmap(device->bam_props.virt_addr); err_map_io_memory: tspp2_clocks_put(device); err_clocks_setup: msm_tspp2_iommu_info_free(device); err_bus_client: if (device->bus_client) msm_bus_scale_unregister_client(device->bus_client); return rc; } /* Device driver remove function */ static int msm_tspp2_remove(struct platform_device *pdev) { int i; int rc = 0; struct tspp2_device *device = platform_get_drvdata(pdev); tspp2_debugfs_exit(device); if (device->tspp2_irq) free_irq(device->tspp2_irq, device); for (i = 0; i < TSPP2_NUM_TSIF_INPUTS; i++) if (device->tsif_devices[i].tsif_irq) free_irq(device->tsif_devices[i].tsif_irq, &device->tsif_devices[i]); /* Unmap memory */ iounmap(device->base); iounmap(device->tsif_devices[0].base); iounmap(device->tsif_devices[1].base); iounmap(device->bam_props.virt_addr); msm_tspp2_iommu_info_free(device); if (device->bus_client) msm_bus_scale_unregister_client(device->bus_client); mutex_destroy(&device->mutex); tspp2_clocks_put(device); return rc; } /* Power Management */ static int tspp2_runtime_suspend(struct device *dev) { int ret = 0; struct tspp2_device *device; struct platform_device *pdev; /* * HW manages power collapse automatically. * Disabling AHB and Core clocsk and "cancelling" bus bandwidth voting. */ pdev = container_of(dev, struct platform_device, dev); device = platform_get_drvdata(pdev); mutex_lock(&device->mutex); if (!device->opened) ret = -EPERM; else ret = tspp2_reg_clock_stop(device); mutex_unlock(&device->mutex); dev_dbg(dev, "%s\n", __func__); return ret; } static int tspp2_runtime_resume(struct device *dev) { int ret = 0; struct tspp2_device *device; struct platform_device *pdev; /* * HW manages power collapse automatically. * Enabling AHB and Core clocks to allow access to unit registers, * and voting for the required bus bandwidth for register access. */ pdev = container_of(dev, struct platform_device, dev); device = platform_get_drvdata(pdev); mutex_lock(&device->mutex); if (!device->opened) ret = -EPERM; else ret = tspp2_reg_clock_start(device); mutex_unlock(&device->mutex); dev_dbg(dev, "%s\n", __func__); return ret; } static const struct dev_pm_ops tspp2_dev_pm_ops = { .runtime_suspend = tspp2_runtime_suspend, .runtime_resume = tspp2_runtime_resume, }; /* Platform driver information */ static struct of_device_id msm_tspp2_match_table[] = { {.compatible = "qcom,msm_tspp2"}, {} }; static struct platform_driver msm_tspp2_driver = { .probe = msm_tspp2_probe, .remove = msm_tspp2_remove, .driver = { .name = "msm_tspp2", .pm = &tspp2_dev_pm_ops, .of_match_table = msm_tspp2_match_table, }, }; /** * tspp2_module_init() - TSPP2 driver module init function. * * Return 0 on success, error value otherwise. */ static int __init tspp2_module_init(void) { int rc; rc = platform_driver_register(&msm_tspp2_driver); if (rc) pr_err("%s: platform_driver_register failed: %d\n", __func__, rc); return rc; } /** * tspp2_module_exit() - TSPP2 driver module exit function. */ static void __exit tspp2_module_exit(void) { platform_driver_unregister(&msm_tspp2_driver); } module_init(tspp2_module_init); module_exit(tspp2_module_exit); MODULE_DESCRIPTION("TSPP2 (Transport Stream Packet Processor v2) platform device driver"); MODULE_LICENSE("GPL v2");