8579 lines
239 KiB
C
8579 lines
239 KiB
C
/* 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 <linux/module.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/io.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/device.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/pm_wakeup.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/msm_ion.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/of.h>
|
|
#include <linux/list.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/iommu.h>
|
|
#include <linux/qcom_iommu.h>
|
|
#include <linux/msm_iommu_domains.h>
|
|
#include <linux/msm-bus.h>
|
|
#include <mach/msm_tspp2.h>
|
|
#include <linux/clk/msm-clk.h>
|
|
|
|
#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");
|