diff --git a/drivers/platform/msm/Kconfig b/drivers/platform/msm/Kconfig index 241bd0b830d4..582f88d01a7d 100644 --- a/drivers/platform/msm/Kconfig +++ b/drivers/platform/msm/Kconfig @@ -132,7 +132,7 @@ config USB_BAM Peripheral-to-Peripheral transfers between the USB and other peripheral. -config QCOM_GENI_SE +config MSM_GENI_SE tristate "QCOM GENI Serial Engine Driver" help This module is used to interact with GENI based Serial Engines on diff --git a/drivers/platform/msm/Makefile b/drivers/platform/msm/Makefile index 128df32b29d2..4d446de54966 100644 --- a/drivers/platform/msm/Makefile +++ b/drivers/platform/msm/Makefile @@ -5,3 +5,4 @@ # obj-$(CONFIG_GSI) += gsi/ obj-$(CONFIG_IPA3) += ipa/ +obj-$(CONFIG_MSM_GENI_SE) += msm-geni-se.o diff --git a/drivers/platform/msm/msm-geni-se.c b/drivers/platform/msm/msm-geni-se.c new file mode 100644 index 000000000000..0dcd03b1e008 --- /dev/null +++ b/drivers/platform/msm/msm-geni-se.c @@ -0,0 +1,1771 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016-2019, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GENI_SE_IOMMU_VA_START (0x40000000) +#define GENI_SE_IOMMU_VA_SIZE (0xC0000000) + +#ifdef CONFIG_ARM64 +#define GENI_SE_DMA_PTR_L(ptr) ((u32)ptr) +#define GENI_SE_DMA_PTR_H(ptr) ((u32)(ptr >> 32)) +#else +#define GENI_SE_DMA_PTR_L(ptr) ((u32)ptr) +#define GENI_SE_DMA_PTR_H(ptr) 0 +#endif + +/* Convert BCM threshold to actual frequency x 4 */ +#define CONV_TO_BW(x) (x*20000*4) +#define NUM_LOG_PAGES 2 +#define MAX_CLK_PERF_LEVEL 32 +static unsigned long default_bus_bw_set[] = {0, 19200000, 50000000, + 100000000, 150000000, 200000000, 236000000}; + +struct bus_vectors { + int src; + int dst; + int ab; + int ib; +}; + +/** + * @struct geni_se_device - Data structure to represent the QUPv3 Core + * @dev: Device pointer of the QUPv3 core. + * @cb_dev: Device pointer of the context bank in the IOMMU. + * @iommu_lock: Lock to protect IOMMU Mapping & attachment. + * @iommu_map: IOMMU map of the memory space supported by this core. + * @iommu_s1_bypass: Bypass IOMMU stage 1 translation. + * @base: Base address of this instance of QUPv3 core. + * @bus_bw: Client handle to the bus bandwidth request. If two + paths present, then this is client handle for core 2x. + * @bus_bw_noc: Client handle to the QUP DDR path bus bandwidth + request. + * @bus_mas_id: Master Endpoint ID for bus BW request. + * @bus_slv_id: Slave Endpoint ID for bus BW request. + * @geni_dev_lock: Lock to protect the bus ab & ib values, list. + * @ab_list_head: Sorted resource list based on average bus BW. + * @ib_list_head: Sorted resource list based on instantaneous bus BW. + * @ab_list_head_noc: Sorted resource list based on average DDR path bus BW. + * @ib_list_head_noc: Sorted resource list based on instantaneous DDR path + bus BW. + * @cur_ab: Current Bus Average BW request value. + * @cur_ib: Current Bus Instantaneous BW request value. + * @cur_ab_noc: Current DDR Bus Average BW request value. + * @cur_ib_noc: Current DDR Bus Instantaneous BW request value. + * @bus_bw_set: Clock plan for the bus driver. + * @bus_bw_set_noc: Clock plan for DDR path. + * @cur_bus_bw_idx: Current index within the bus clock plan. + * @cur_bus_bw_idx_noc: Current index within the DDR path clock plan. + * @num_clk_levels: Number of valid clock levels in clk_perf_tbl. + * @clk_perf_tbl: Table of clock frequency input to Serial Engine clock. + * @log_ctx: Logging context to hold the debug information. + * @vectors: Structure to store Master End and Slave End IDs for + QUPv3 clock and DDR path bus BW request. + * @num_paths: Two paths. QUPv3 clock and DDR paths. + * @update: Usecase index for icb voting. + * @vote_for_bw: To check if we have to vote for BW or BCM threashold + in ab/ib ICB voting. + */ +struct geni_se_device { + struct device *dev; + struct device *cb_dev; + struct mutex iommu_lock; + struct dma_iommu_mapping *iommu_map; + bool iommu_s1_bypass; + void __iomem *base; + struct icc_path *bus_bw; + struct icc_path *bus_bw_noc; + u32 bus_mas_id; + u32 bus_slv_id; + struct mutex geni_dev_lock; + struct list_head ab_list_head; + struct list_head ib_list_head; + struct list_head ab_list_head_noc; + struct list_head ib_list_head_noc; + unsigned long cur_ab; + unsigned long cur_ib; + unsigned long cur_ab_noc; + unsigned long cur_ib_noc; + int bus_bw_set_size; + int bus_bw_set_size_noc; + unsigned long *bus_bw_set; + unsigned long *bus_bw_set_noc; + int cur_bus_bw_idx; + int cur_bus_bw_idx_noc; + unsigned int num_clk_levels; + unsigned long *clk_perf_tbl; + void *log_ctx; + struct bus_vectors *vectors; + int num_paths; + int update; + bool vote_for_bw; +}; + +/* Offset of QUPV3 Hardware Version Register */ +#define QUPV3_HW_VER (0x4) + +#define HW_VER_MAJOR_MASK GENMASK(31, 28) +#define HW_VER_MAJOR_SHFT 28 +#define HW_VER_MINOR_MASK GENMASK(27, 16) +#define HW_VER_MINOR_SHFT 16 +#define HW_VER_STEP_MASK GENMASK(15, 0) + +static int geni_se_iommu_map_and_attach(struct geni_se_device *geni_se_dev); + +/** + * geni_read_reg_nolog() - Helper function to read from a GENI register + * @base: Base address of the serial engine's register block. + * @offset: Offset within the serial engine's register block. + * + * Return: Return the contents of the register. + */ +unsigned int geni_read_reg_nolog(void __iomem *base, int offset) +{ + return readl_relaxed_no_log(base + offset); +} +EXPORT_SYMBOL(geni_read_reg_nolog); + +/** + * geni_write_reg_nolog() - Helper function to write into a GENI register + * @value: Value to be written into the register. + * @base: Base address of the serial engine's register block. + * @offset: Offset within the serial engine's register block. + */ +void geni_write_reg_nolog(unsigned int value, void __iomem *base, int offset) +{ + return writel_relaxed_no_log(value, (base + offset)); +} +EXPORT_SYMBOL(geni_write_reg_nolog); + +/** + * geni_read_reg() - Helper function to read from a GENI register + * @base: Base address of the serial engine's register block. + * @offset: Offset within the serial engine's register block. + * + * Return: Return the contents of the register. + */ +unsigned int geni_read_reg(void __iomem *base, int offset) +{ + return readl_relaxed(base + offset); +} +EXPORT_SYMBOL(geni_read_reg); + +/** + * geni_write_reg() - Helper function to write into a GENI register + * @value: Value to be written into the register. + * @base: Base address of the serial engine's register block. + * @offset: Offset within the serial engine's register block. + */ +void geni_write_reg(unsigned int value, void __iomem *base, int offset) +{ + return writel_relaxed(value, (base + offset)); +} +EXPORT_SYMBOL(geni_write_reg); + +/** + * get_se_proto() - Read the protocol configured for a serial engine + * @base: Base address of the serial engine's register block. + * + * Return: Protocol value as configured in the serial engine. + */ +int get_se_proto(void __iomem *base) +{ + int proto; + + proto = ((geni_read_reg(base, GENI_FW_REVISION_RO) + & FW_REV_PROTOCOL_MSK) >> FW_REV_PROTOCOL_SHFT); + return proto; +} +EXPORT_SYMBOL(get_se_proto); + +/** + * get_se_m_fw() - Read the Firmware ver for the Main sequencer engine + * @base: Base address of the serial engine's register block. + * + * Return: Firmware version for the Main sequencer engine + */ +int get_se_m_fw(void __iomem *base) +{ + int fw_ver_m; + + fw_ver_m = ((geni_read_reg(base, GENI_FW_REVISION_RO) + & FW_REV_VERSION_MSK)); + return fw_ver_m; +} +EXPORT_SYMBOL(get_se_m_fw); + +/** + * get_se_s_fw() - Read the Firmware ver for the Secondry sequencer engine + * @base: Base address of the serial engine's register block. + * + * Return: Firmware version for the Secondry sequencer engine + */ +int get_se_s_fw(void __iomem *base) +{ + int fw_ver_s; + + fw_ver_s = ((geni_read_reg(base, GENI_FW_S_REVISION_RO) + & FW_REV_VERSION_MSK)); + return fw_ver_s; +} +EXPORT_SYMBOL(get_se_s_fw); + +static int se_geni_irq_en(void __iomem *base) +{ + unsigned int common_geni_m_irq_en; + unsigned int common_geni_s_irq_en; + + common_geni_m_irq_en = geni_read_reg(base, SE_GENI_M_IRQ_EN); + common_geni_s_irq_en = geni_read_reg(base, SE_GENI_S_IRQ_EN); + /* Common to all modes */ + common_geni_m_irq_en |= M_COMMON_GENI_M_IRQ_EN; + common_geni_s_irq_en |= S_COMMON_GENI_S_IRQ_EN; + + geni_write_reg(common_geni_m_irq_en, base, SE_GENI_M_IRQ_EN); + geni_write_reg(common_geni_s_irq_en, base, SE_GENI_S_IRQ_EN); + return 0; +} + + +static void se_set_rx_rfr_wm(void __iomem *base, unsigned int rx_wm, + unsigned int rx_rfr) +{ + geni_write_reg(rx_wm, base, SE_GENI_RX_WATERMARK_REG); + geni_write_reg(rx_rfr, base, SE_GENI_RX_RFR_WATERMARK_REG); +} + +static int se_io_set_mode(void __iomem *base) +{ + unsigned int io_mode; + unsigned int geni_dma_mode; + + io_mode = geni_read_reg(base, SE_IRQ_EN); + geni_dma_mode = geni_read_reg(base, SE_GENI_DMA_MODE_EN); + + io_mode |= (GENI_M_IRQ_EN | GENI_S_IRQ_EN); + io_mode |= (DMA_TX_IRQ_EN | DMA_RX_IRQ_EN); + geni_dma_mode &= ~GENI_DMA_MODE_EN; + + geni_write_reg(io_mode, base, SE_IRQ_EN); + geni_write_reg(geni_dma_mode, base, SE_GENI_DMA_MODE_EN); + geni_write_reg(0, base, SE_GSI_EVENT_EN); + return 0; +} + +static void se_io_init(void __iomem *base) +{ + unsigned int io_op_ctrl; + unsigned int geni_cgc_ctrl; + unsigned int dma_general_cfg; + + geni_cgc_ctrl = geni_read_reg(base, GENI_CGC_CTRL); + dma_general_cfg = geni_read_reg(base, SE_DMA_GENERAL_CFG); + geni_cgc_ctrl |= DEFAULT_CGC_EN; + dma_general_cfg |= (AHB_SEC_SLV_CLK_CGC_ON | DMA_AHB_SLV_CFG_ON | + DMA_TX_CLK_CGC_ON | DMA_RX_CLK_CGC_ON); + io_op_ctrl = DEFAULT_IO_OUTPUT_CTRL_MSK; + geni_write_reg(geni_cgc_ctrl, base, GENI_CGC_CTRL); + geni_write_reg(dma_general_cfg, base, SE_DMA_GENERAL_CFG); + + geni_write_reg(io_op_ctrl, base, GENI_OUTPUT_CTRL); + geni_write_reg(FORCE_DEFAULT, base, GENI_FORCE_DEFAULT_REG); +} + +/** + * geni_se_init() - Initialize the GENI Serial Engine + * @base: Base address of the serial engine's register block. + * @rx_wm: Receive watermark to be configured. + * @rx_rfr_wm: Ready-for-receive watermark to be configured. + * + * This function is used to initialize the GENI serial engine, configure + * receive watermark and ready-for-receive watermarks. + * + * Return: 0 on success, standard Linux error codes on failure/error. + */ +int geni_se_init(void __iomem *base, unsigned int rx_wm, unsigned int rx_rfr) +{ + int ret; + + se_io_init(base); + ret = se_io_set_mode(base); + if (ret) + return ret; + + se_set_rx_rfr_wm(base, rx_wm, rx_rfr); + ret = se_geni_irq_en(base); + return ret; +} +EXPORT_SYMBOL(geni_se_init); + +static int geni_se_select_fifo_mode(void __iomem *base) +{ + int proto = get_se_proto(base); + unsigned int common_geni_m_irq_en; + unsigned int common_geni_s_irq_en; + unsigned int geni_dma_mode; + + geni_write_reg(0, base, SE_GSI_EVENT_EN); + geni_write_reg(0xFFFFFFFF, base, SE_GENI_M_IRQ_CLEAR); + geni_write_reg(0xFFFFFFFF, base, SE_GENI_S_IRQ_CLEAR); + geni_write_reg(0xFFFFFFFF, base, SE_DMA_TX_IRQ_CLR); + geni_write_reg(0xFFFFFFFF, base, SE_DMA_RX_IRQ_CLR); + geni_write_reg(0xFFFFFFFF, base, SE_IRQ_EN); + + common_geni_m_irq_en = geni_read_reg(base, SE_GENI_M_IRQ_EN); + common_geni_s_irq_en = geni_read_reg(base, SE_GENI_S_IRQ_EN); + geni_dma_mode = geni_read_reg(base, SE_GENI_DMA_MODE_EN); + if (proto != UART) { + common_geni_m_irq_en |= + (M_CMD_DONE_EN | M_TX_FIFO_WATERMARK_EN | + M_RX_FIFO_WATERMARK_EN | M_RX_FIFO_LAST_EN); + common_geni_s_irq_en |= S_CMD_DONE_EN; + } + geni_dma_mode &= ~GENI_DMA_MODE_EN; + + geni_write_reg(common_geni_m_irq_en, base, SE_GENI_M_IRQ_EN); + geni_write_reg(common_geni_s_irq_en, base, SE_GENI_S_IRQ_EN); + geni_write_reg(geni_dma_mode, base, SE_GENI_DMA_MODE_EN); + + if (proto == I3C) + geni_write_reg(0x3, base, GENI_I3C_IBI_LEGACY); + return 0; +} + +static int geni_se_select_dma_mode(void __iomem *base) +{ + int proto = get_se_proto(base); + unsigned int geni_dma_mode = 0; + unsigned int common_geni_m_irq_en; + + geni_write_reg(0, base, SE_GSI_EVENT_EN); + geni_write_reg(0xFFFFFFFF, base, SE_GENI_M_IRQ_CLEAR); + geni_write_reg(0xFFFFFFFF, base, SE_GENI_S_IRQ_CLEAR); + geni_write_reg(0xFFFFFFFF, base, SE_DMA_TX_IRQ_CLR); + geni_write_reg(0xFFFFFFFF, base, SE_DMA_RX_IRQ_CLR); + geni_write_reg(0xFFFFFFFF, base, SE_IRQ_EN); + + common_geni_m_irq_en = geni_read_reg(base, SE_GENI_M_IRQ_EN); + if (proto != UART) + common_geni_m_irq_en &= + ~(M_TX_FIFO_WATERMARK_EN | M_RX_FIFO_WATERMARK_EN); + + geni_write_reg(common_geni_m_irq_en, base, SE_GENI_M_IRQ_EN); + geni_dma_mode = geni_read_reg(base, SE_GENI_DMA_MODE_EN); + geni_dma_mode |= GENI_DMA_MODE_EN; + geni_write_reg(geni_dma_mode, base, SE_GENI_DMA_MODE_EN); + return 0; +} + +static int geni_se_select_gsi_mode(void __iomem *base) +{ + unsigned int geni_dma_mode = 0; + unsigned int gsi_event_en = 0; + unsigned int common_geni_m_irq_en = 0; + unsigned int common_geni_s_irq_en = 0; + + common_geni_m_irq_en = geni_read_reg(base, SE_GENI_M_IRQ_EN); + common_geni_s_irq_en = geni_read_reg(base, SE_GENI_S_IRQ_EN); + common_geni_m_irq_en &= + ~(M_CMD_DONE_EN | M_TX_FIFO_WATERMARK_EN | + M_RX_FIFO_WATERMARK_EN | M_RX_FIFO_LAST_EN); + common_geni_s_irq_en &= ~S_CMD_DONE_EN; + geni_dma_mode = geni_read_reg(base, SE_GENI_DMA_MODE_EN); + gsi_event_en = geni_read_reg(base, SE_GSI_EVENT_EN); + + geni_dma_mode |= GENI_DMA_MODE_EN; + gsi_event_en |= (DMA_RX_EVENT_EN | DMA_TX_EVENT_EN | + GENI_M_EVENT_EN | GENI_S_EVENT_EN); + + geni_write_reg(0, base, SE_IRQ_EN); + geni_write_reg(common_geni_s_irq_en, base, SE_GENI_S_IRQ_EN); + geni_write_reg(common_geni_m_irq_en, base, SE_GENI_M_IRQ_EN); + geni_write_reg(0xFFFFFFFF, base, SE_GENI_M_IRQ_CLEAR); + geni_write_reg(0xFFFFFFFF, base, SE_GENI_S_IRQ_CLEAR); + geni_write_reg(0xFFFFFFFF, base, SE_DMA_TX_IRQ_CLR); + geni_write_reg(0xFFFFFFFF, base, SE_DMA_RX_IRQ_CLR); + geni_write_reg(geni_dma_mode, base, SE_GENI_DMA_MODE_EN); + geni_write_reg(gsi_event_en, base, SE_GSI_EVENT_EN); + return 0; + +} + +/** + * geni_se_select_mode() - Select the serial engine transfer mode + * @base: Base address of the serial engine's register block. + * @mode: Transfer mode to be selected. + * + * Return: 0 on success, standard Linux error codes on failure. + */ +int geni_se_select_mode(void __iomem *base, int mode) +{ + int ret = 0; + + switch (mode) { + case FIFO_MODE: + geni_se_select_fifo_mode(base); + break; + case SE_DMA: + geni_se_select_dma_mode(base); + break; + case GSI_DMA: + geni_se_select_gsi_mode(base); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} +EXPORT_SYMBOL(geni_se_select_mode); + +/** + * geni_setup_m_cmd() - Setup the primary sequencer + * @base: Base address of the serial engine's register block. + * @cmd: Command/Operation to setup in the primary sequencer. + * @params: Parameter for the sequencer command. + * + * This function is used to configure the primary sequencer with the + * command and its assoicated parameters. + */ +void geni_setup_m_cmd(void __iomem *base, u32 cmd, u32 params) +{ + u32 m_cmd = (cmd << M_OPCODE_SHFT); + + m_cmd |= (params & M_PARAMS_MSK); + geni_write_reg(m_cmd, base, SE_GENI_M_CMD0); +} +EXPORT_SYMBOL(geni_setup_m_cmd); + +/** + * geni_setup_s_cmd() - Setup the secondary sequencer + * @base: Base address of the serial engine's register block. + * @cmd: Command/Operation to setup in the secondary sequencer. + * @params: Parameter for the sequencer command. + * + * This function is used to configure the secondary sequencer with the + * command and its assoicated parameters. + */ +void geni_setup_s_cmd(void __iomem *base, u32 cmd, u32 params) +{ + u32 s_cmd = geni_read_reg(base, SE_GENI_S_CMD0); + + s_cmd &= ~(S_OPCODE_MSK | S_PARAMS_MSK); + s_cmd |= (cmd << S_OPCODE_SHFT); + s_cmd |= (params & S_PARAMS_MSK); + geni_write_reg(s_cmd, base, SE_GENI_S_CMD0); +} +EXPORT_SYMBOL(geni_setup_s_cmd); + +/** + * geni_cancel_m_cmd() - Cancel the command configured in the primary sequencer + * @base: Base address of the serial engine's register block. + * + * This function is used to cancel the currently configured command in the + * primary sequencer. + */ +void geni_cancel_m_cmd(void __iomem *base) +{ + geni_write_reg(M_GENI_CMD_CANCEL, base, SE_GENI_M_CMD_CTRL_REG); +} +EXPORT_SYMBOL(geni_cancel_m_cmd); + +/** + * geni_cancel_s_cmd() - Cancel the command configured in the secondary + * sequencer + * @base: Base address of the serial engine's register block. + * + * This function is used to cancel the currently configured command in the + * secondary sequencer. + */ +void geni_cancel_s_cmd(void __iomem *base) +{ + geni_write_reg(S_GENI_CMD_CANCEL, base, SE_GENI_S_CMD_CTRL_REG); +} +EXPORT_SYMBOL(geni_cancel_s_cmd); + +/** + * geni_abort_m_cmd() - Abort the command configured in the primary sequencer + * @base: Base address of the serial engine's register block. + * + * This function is used to force abort the currently configured command in the + * primary sequencer. + */ +void geni_abort_m_cmd(void __iomem *base) +{ + geni_write_reg(M_GENI_CMD_ABORT, base, SE_GENI_M_CMD_CTRL_REG); +} +EXPORT_SYMBOL(geni_abort_m_cmd); + +/** + * geni_abort_s_cmd() - Abort the command configured in the secondary + * sequencer + * @base: Base address of the serial engine's register block. + * + * This function is used to force abort the currently configured command in the + * secondary sequencer. + */ +void geni_abort_s_cmd(void __iomem *base) +{ + geni_write_reg(S_GENI_CMD_ABORT, base, SE_GENI_S_CMD_CTRL_REG); +} +EXPORT_SYMBOL(geni_abort_s_cmd); + +/** + * get_tx_fifo_depth() - Get the TX fifo depth of the serial engine + * @base: Base address of the serial engine's register block. + * + * This function is used to get the depth i.e. number of elements in the + * TX fifo of the serial engine. + * + * Return: TX fifo depth in units of FIFO words. + */ +int get_tx_fifo_depth(void __iomem *base) +{ + int tx_fifo_depth; + + tx_fifo_depth = ((geni_read_reg(base, SE_HW_PARAM_0) + & TX_FIFO_DEPTH_MSK) >> TX_FIFO_DEPTH_SHFT); + return tx_fifo_depth; +} +EXPORT_SYMBOL(get_tx_fifo_depth); + +/** + * get_tx_fifo_width() - Get the TX fifo width of the serial engine + * @base: Base address of the serial engine's register block. + * + * This function is used to get the width i.e. word size per element in the + * TX fifo of the serial engine. + * + * Return: TX fifo width in bits + */ +int get_tx_fifo_width(void __iomem *base) +{ + int tx_fifo_width; + + tx_fifo_width = ((geni_read_reg(base, SE_HW_PARAM_0) + & TX_FIFO_WIDTH_MSK) >> TX_FIFO_WIDTH_SHFT); + return tx_fifo_width; +} +EXPORT_SYMBOL(get_tx_fifo_width); + +/** + * get_rx_fifo_depth() - Get the RX fifo depth of the serial engine + * @base: Base address of the serial engine's register block. + * + * This function is used to get the depth i.e. number of elements in the + * RX fifo of the serial engine. + * + * Return: RX fifo depth in units of FIFO words + */ +int get_rx_fifo_depth(void __iomem *base) +{ + int rx_fifo_depth; + + rx_fifo_depth = ((geni_read_reg(base, SE_HW_PARAM_1) + & RX_FIFO_DEPTH_MSK) >> RX_FIFO_DEPTH_SHFT); + return rx_fifo_depth; +} +EXPORT_SYMBOL(get_rx_fifo_depth); + +/** + * se_get_packing_config() - Get the packing configuration based on input + * @bpw: Bits of data per transfer word. + * @pack_words: Number of words per fifo element. + * @msb_to_lsb: Transfer from MSB to LSB or vice-versa. + * @cfg0: Output buffer to hold the first half of configuration. + * @cfg1: Output buffer to hold the second half of configuration. + * + * This function is used to calculate the packing configuration based on + * the input packing requirement and the configuration logic. + */ +void se_get_packing_config(int bpw, int pack_words, bool msb_to_lsb, + unsigned long *cfg0, unsigned long *cfg1) +{ + u32 cfg[4] = {0}; + int len; + int temp_bpw = bpw; + int idx_start = (msb_to_lsb ? (bpw - 1) : 0); + int idx = idx_start; + int idx_delta = (msb_to_lsb ? -BITS_PER_BYTE : BITS_PER_BYTE); + int ceil_bpw = ((bpw & (BITS_PER_BYTE - 1)) ? + ((bpw & ~(BITS_PER_BYTE - 1)) + BITS_PER_BYTE) : bpw); + int iter = (ceil_bpw * pack_words) >> 3; + int i; + + if (unlikely(iter <= 0 || iter > 4)) { + *cfg0 = 0; + *cfg1 = 0; + return; + } + + for (i = 0; i < iter; i++) { + len = (temp_bpw < BITS_PER_BYTE) ? + (temp_bpw - 1) : BITS_PER_BYTE - 1; + cfg[i] = ((idx << 5) | (msb_to_lsb << 4) | (len << 1)); + idx = ((temp_bpw - BITS_PER_BYTE) <= 0) ? + ((i + 1) * BITS_PER_BYTE) + idx_start : + idx + idx_delta; + temp_bpw = ((temp_bpw - BITS_PER_BYTE) <= 0) ? + bpw : (temp_bpw - BITS_PER_BYTE); + } + cfg[iter - 1] |= 1; + *cfg0 = cfg[0] | (cfg[1] << 10); + *cfg1 = cfg[2] | (cfg[3] << 10); +} +EXPORT_SYMBOL(se_get_packing_config); + +/** + * se_config_packing() - Packing configuration of the serial engine + * @base: Base address of the serial engine's register block. + * @bpw: Bits of data per transfer word. + * @pack_words: Number of words per fifo element. + * @msb_to_lsb: Transfer from MSB to LSB or vice-versa. + * + * This function is used to configure the packing rules for the current + * transfer. + */ +void se_config_packing(void __iomem *base, int bpw, + int pack_words, bool msb_to_lsb) +{ + unsigned long cfg0, cfg1; + + se_get_packing_config(bpw, pack_words, msb_to_lsb, &cfg0, &cfg1); + geni_write_reg(cfg0, base, SE_GENI_TX_PACKING_CFG0); + geni_write_reg(cfg1, base, SE_GENI_TX_PACKING_CFG1); + geni_write_reg(cfg0, base, SE_GENI_RX_PACKING_CFG0); + geni_write_reg(cfg1, base, SE_GENI_RX_PACKING_CFG1); + if (pack_words || bpw == 32) + geni_write_reg((bpw >> 4), base, SE_GENI_BYTE_GRAN); +} +EXPORT_SYMBOL(se_config_packing); + +static bool geni_se_check_bus_bw(struct geni_se_device *geni_se_dev) +{ + int i; + int new_bus_bw_idx = geni_se_dev->bus_bw_set_size - 1; + unsigned long new_bus_bw; + bool bus_bw_update = false; + /* Convert agg ab into bytes per second */ + unsigned long new_ab_in_hz = DEFAULT_BUS_WIDTH * + ((2*geni_se_dev->cur_ab)*10000); + + new_bus_bw = max(geni_se_dev->cur_ib, new_ab_in_hz) / + DEFAULT_BUS_WIDTH; + for (i = 0; i < geni_se_dev->bus_bw_set_size; i++) { + if (geni_se_dev->bus_bw_set[i] >= new_bus_bw) { + new_bus_bw_idx = i; + break; + } + } + + if (geni_se_dev->cur_bus_bw_idx != new_bus_bw_idx) { + geni_se_dev->cur_bus_bw_idx = new_bus_bw_idx; + bus_bw_update = true; + } + return bus_bw_update; +} + +static bool geni_se_check_bus_bw_noc(struct geni_se_device *geni_se_dev) +{ + int i; + int new_bus_bw_idx = geni_se_dev->bus_bw_set_size_noc - 1; + unsigned long new_bus_bw; + bool bus_bw_update = false; + + new_bus_bw = max(geni_se_dev->cur_ib_noc, geni_se_dev->cur_ab_noc) / + DEFAULT_BUS_WIDTH; + + for (i = 0; i < geni_se_dev->bus_bw_set_size_noc; i++) { + if (geni_se_dev->bus_bw_set_noc[i] >= new_bus_bw) { + new_bus_bw_idx = i; + break; + } + } + + if (geni_se_dev->cur_bus_bw_idx_noc != new_bus_bw_idx) { + geni_se_dev->cur_bus_bw_idx_noc = new_bus_bw_idx; + bus_bw_update = true; + } + + return bus_bw_update; +} + +static int geni_se_rmv_ab_ib(struct geni_se_device *geni_se_dev, + struct se_geni_rsc *rsc) +{ + struct se_geni_rsc *tmp; + bool bus_bw_update = false; + bool bus_bw_update_noc = false; + int ret = 0; + + if (unlikely(list_empty(&rsc->ab_list) || list_empty(&rsc->ib_list))) + return -EINVAL; + + mutex_lock(&geni_se_dev->geni_dev_lock); + + list_del_init(&rsc->ab_list); + geni_se_dev->cur_ab -= rsc->ab; + + list_del_init(&rsc->ib_list); + tmp = list_first_entry_or_null(&geni_se_dev->ib_list_head, + struct se_geni_rsc, ib_list); + if (tmp && tmp->ib != geni_se_dev->cur_ib) + geni_se_dev->cur_ib = tmp->ib; + else if (!tmp && geni_se_dev->cur_ib) + geni_se_dev->cur_ib = 0; + + bus_bw_update = geni_se_check_bus_bw(geni_se_dev); + + geni_se_dev->vectors[0].ab = geni_se_dev->vote_for_bw ? + CONV_TO_BW(geni_se_dev->cur_ab) : geni_se_dev->cur_ab; + geni_se_dev->vectors[0].ib = geni_se_dev->vote_for_bw ? + CONV_TO_BW(geni_se_dev->cur_ib) : geni_se_dev->cur_ib; + + if (bus_bw_update) + ret = icc_set_bw(geni_se_dev->bus_bw, + geni_se_dev->vectors[0].ab, + geni_se_dev->vectors[0].ib); + + GENI_SE_DBG(geni_se_dev->log_ctx, false, NULL, + "%s: %s: cur_ab_ib(%lu:%lu) req_ab_ib(%lu:%lu) %d\n", + __func__, dev_name(rsc->ctrl_dev), geni_se_dev->cur_ab, + geni_se_dev->cur_ib, rsc->ab, rsc->ib, bus_bw_update); + + + if (geni_se_dev->num_paths == 2) { + if (unlikely(list_empty(&rsc->ab_list_noc) || + list_empty(&rsc->ib_list_noc))) { + mutex_unlock(&geni_se_dev->geni_dev_lock); + return -EINVAL; + } + + list_del_init(&rsc->ab_list_noc); + geni_se_dev->cur_ab_noc -= rsc->ab_noc; + + list_del_init(&rsc->ib_list_noc); + tmp = list_first_entry_or_null(&geni_se_dev->ib_list_head_noc, + struct se_geni_rsc, ib_list_noc); + if (tmp && tmp->ib_noc != geni_se_dev->cur_ib_noc) + geni_se_dev->cur_ib_noc = tmp->ib_noc; + else if (!tmp && geni_se_dev->cur_ib_noc) + geni_se_dev->cur_ib_noc = 0; + + bus_bw_update_noc = geni_se_check_bus_bw_noc(geni_se_dev); + geni_se_dev->vectors[1].ab = geni_se_dev->cur_ab_noc; + geni_se_dev->vectors[1].ib = geni_se_dev->cur_ib_noc; + + if (bus_bw_update_noc) + ret = icc_set_bw(geni_se_dev->bus_bw_noc, + geni_se_dev->vectors[1].ab, + geni_se_dev->vectors[1].ib); + + GENI_SE_DBG(geni_se_dev->log_ctx, false, NULL, + "%s: %s: cur_ab_ib_noc(%lu:%lu) req_ab_ib_noc(%lu:%lu) %d\n", + __func__, dev_name(rsc->ctrl_dev), + geni_se_dev->cur_ab_noc, geni_se_dev->cur_ib_noc, + rsc->ab_noc, rsc->ib_noc, bus_bw_update_noc); + } + mutex_unlock(&geni_se_dev->geni_dev_lock); + return ret; +} + +/** + * se_geni_clks_off() - Turn off clocks associated with the serial + * engine + * @rsc: Handle to resources associated with the serial engine. + * + * Return: 0 on success, standard Linux error codes on failure/error. + */ +int se_geni_clks_off(struct se_geni_rsc *rsc) +{ + int ret = 0; + struct geni_se_device *geni_se_dev; + + if (unlikely(!rsc || !rsc->wrapper_dev)) + return -EINVAL; + + geni_se_dev = dev_get_drvdata(rsc->wrapper_dev); + if (unlikely(!geni_se_dev || !(geni_se_dev->bus_bw || + geni_se_dev->bus_bw_noc))) + return -ENODEV; + + clk_disable_unprepare(rsc->se_clk); + clk_disable_unprepare(rsc->s_ahb_clk); + clk_disable_unprepare(rsc->m_ahb_clk); + + ret = geni_se_rmv_ab_ib(geni_se_dev, rsc); + if (ret) + GENI_SE_ERR(geni_se_dev->log_ctx, false, NULL, + "%s: Error %d during bus_bw_update\n", __func__, ret); + + return ret; +} +EXPORT_SYMBOL(se_geni_clks_off); + +/** + * se_geni_resources_off() - Turn off resources associated with the serial + * engine + * @rsc: Handle to resources associated with the serial engine. + * + * Return: 0 on success, standard Linux error codes on failure/error. + */ +int se_geni_resources_off(struct se_geni_rsc *rsc) +{ + int ret = 0; + struct geni_se_device *geni_se_dev; + + if (unlikely(!rsc || !rsc->wrapper_dev)) + return -EINVAL; + + geni_se_dev = dev_get_drvdata(rsc->wrapper_dev); + if (unlikely(!geni_se_dev || + !(geni_se_dev->bus_bw || + geni_se_dev->bus_bw_noc))) + return -ENODEV; + + ret = se_geni_clks_off(rsc); + if (ret) + GENI_SE_ERR(geni_se_dev->log_ctx, false, NULL, + "%s: Error %d turning off clocks\n", __func__, ret); + ret = pinctrl_select_state(rsc->geni_pinctrl, rsc->geni_gpio_sleep); + if (ret) + GENI_SE_ERR(geni_se_dev->log_ctx, false, NULL, + "%s: Error %d pinctrl_select_state\n", __func__, ret); + return ret; +} +EXPORT_SYMBOL(se_geni_resources_off); + +static int geni_se_add_ab_ib(struct geni_se_device *geni_se_dev, + struct se_geni_rsc *rsc) +{ + struct se_geni_rsc *tmp = NULL; + struct list_head *ins_list_head; + struct list_head *ins_list_head_noc; + bool bus_bw_update = false; + bool bus_bw_update_noc = false; + int ret = 0; + + mutex_lock(&geni_se_dev->geni_dev_lock); + + list_add(&rsc->ab_list, &geni_se_dev->ab_list_head); + geni_se_dev->cur_ab += rsc->ab; + + ins_list_head = &geni_se_dev->ib_list_head; + list_for_each_entry(tmp, &geni_se_dev->ib_list_head, ib_list) { + if (tmp->ib < rsc->ib) + break; + ins_list_head = &tmp->ib_list; + } + list_add(&rsc->ib_list, ins_list_head); + /* Currently inserted node has greater average BW value */ + if (ins_list_head == &geni_se_dev->ib_list_head) + geni_se_dev->cur_ib = rsc->ib; + + bus_bw_update = geni_se_check_bus_bw(geni_se_dev); + + geni_se_dev->vectors[0].ab = geni_se_dev->vote_for_bw ? + CONV_TO_BW(geni_se_dev->cur_ab) : geni_se_dev->cur_ab; + geni_se_dev->vectors[0].ib = geni_se_dev->vote_for_bw ? + CONV_TO_BW(geni_se_dev->cur_ib) : geni_se_dev->cur_ib; + + if (bus_bw_update) + ret = icc_set_bw(geni_se_dev->bus_bw, + geni_se_dev->vectors[0].ab, + geni_se_dev->vectors[0].ib); + + GENI_SE_DBG(geni_se_dev->log_ctx, false, NULL, + "%s: %s: cur_ab_ib(%lu:%lu) req_ab_ib(%lu:%lu) %d\n", + __func__, dev_name(rsc->ctrl_dev), + geni_se_dev->cur_ab, geni_se_dev->cur_ib, + rsc->ab, rsc->ib, bus_bw_update); + + + if (geni_se_dev->num_paths == 2) { + + list_add(&rsc->ab_list_noc, &geni_se_dev->ab_list_head_noc); + geni_se_dev->cur_ab_noc += rsc->ab_noc; + ins_list_head_noc = &geni_se_dev->ib_list_head_noc; + + list_for_each_entry(tmp, &geni_se_dev->ib_list_head_noc, + ib_list_noc) { + if (tmp->ib < rsc->ib) + break; + ins_list_head_noc = &tmp->ib_list_noc; + } + list_add(&rsc->ib_list_noc, ins_list_head_noc); + + if (ins_list_head_noc == &geni_se_dev->ib_list_head_noc) + geni_se_dev->cur_ib_noc = rsc->ib_noc; + + bus_bw_update_noc = geni_se_check_bus_bw_noc(geni_se_dev); + geni_se_dev->vectors[1].ab = geni_se_dev->cur_ab_noc; + geni_se_dev->vectors[1].ib = geni_se_dev->cur_ib_noc; + + if (bus_bw_update_noc) + ret = icc_set_bw(geni_se_dev->bus_bw_noc, + geni_se_dev->vectors[1].ab, + geni_se_dev->vectors[1].ib); + + GENI_SE_DBG(geni_se_dev->log_ctx, false, NULL, + "%s: %s: cur_ab_ib_noc(%lu:%lu) req_ab_ib_noc(%lu:%lu) %d\n", + __func__, dev_name(rsc->ctrl_dev), + geni_se_dev->cur_ab_noc, geni_se_dev->cur_ib_noc, + rsc->ab_noc, rsc->ib_noc, bus_bw_update_noc); + } + mutex_unlock(&geni_se_dev->geni_dev_lock); + return ret; +} + +/** + * se_geni_clks_on() - Turn on clocks associated with the serial + * engine + * @rsc: Handle to resources associated with the serial engine. + * + * Return: 0 on success, standard Linux error codes on failure/error. + */ +int se_geni_clks_on(struct se_geni_rsc *rsc) +{ + int ret = 0; + struct geni_se_device *geni_se_dev; + + if (unlikely(!rsc || !rsc->wrapper_dev)) + return -EINVAL; + + geni_se_dev = dev_get_drvdata(rsc->wrapper_dev); + if (unlikely(!geni_se_dev)) + return -EPROBE_DEFER; + + ret = geni_se_add_ab_ib(geni_se_dev, rsc); + if (ret) { + GENI_SE_ERR(geni_se_dev->log_ctx, false, NULL, + "%s: Error %d during bus_bw_update\n", __func__, ret); + return ret; + } + + ret = clk_prepare_enable(rsc->m_ahb_clk); + if (ret) + goto clks_on_err1; + + ret = clk_prepare_enable(rsc->s_ahb_clk); + if (ret) + goto clks_on_err2; + + ret = clk_prepare_enable(rsc->se_clk); + if (ret) + goto clks_on_err3; + return 0; + +clks_on_err3: + clk_disable_unprepare(rsc->s_ahb_clk); +clks_on_err2: + clk_disable_unprepare(rsc->m_ahb_clk); +clks_on_err1: + geni_se_rmv_ab_ib(geni_se_dev, rsc); + return ret; +} +EXPORT_SYMBOL(se_geni_clks_on); + +/** + * se_geni_resources_on() - Turn on resources associated with the serial + * engine + * @rsc: Handle to resources associated with the serial engine. + * + * Return: 0 on success, standard Linux error codes on failure/error. + */ +int se_geni_resources_on(struct se_geni_rsc *rsc) +{ + int ret = 0; + struct geni_se_device *geni_se_dev; + + if (unlikely(!rsc || !rsc->wrapper_dev)) + return -EINVAL; + + geni_se_dev = dev_get_drvdata(rsc->wrapper_dev); + if (unlikely(!geni_se_dev)) + return -EPROBE_DEFER; + + ret = pinctrl_select_state(rsc->geni_pinctrl, rsc->geni_gpio_active); + if (ret) { + GENI_SE_ERR(geni_se_dev->log_ctx, false, NULL, + "%s: Error %d pinctrl_select_state\n", __func__, ret); + return ret; + } + + ret = se_geni_clks_on(rsc); + if (ret) { + GENI_SE_ERR(geni_se_dev->log_ctx, false, NULL, + "%s: Error %d during clks_on\n", __func__, ret); + pinctrl_select_state(rsc->geni_pinctrl, rsc->geni_gpio_sleep); + } + + return ret; +} +EXPORT_SYMBOL(se_geni_resources_on); + +/** + * geni_se_resources_init() - Init the SE resource structure + * @rsc: SE resource structure to be initialized. + * @ab: Initial Average bus bandwidth request value. + * @ib: Initial Instantaneous bus bandwidth request value. + * + * Return: 0 on success, standard Linux error codes on failure. + */ +int geni_se_resources_init(struct se_geni_rsc *rsc, + unsigned long ab, unsigned long ib) +{ + struct geni_se_device *geni_se_dev; + int ret = 0; + const char *mode = NULL; + + if (unlikely(!rsc || !rsc->wrapper_dev)) + return -EINVAL; + + geni_se_dev = dev_get_drvdata(rsc->wrapper_dev); + if (unlikely(!geni_se_dev)) + return -EPROBE_DEFER; + + if (geni_se_dev->num_paths == 2) { + if (unlikely(!(geni_se_dev->bus_bw))) { + geni_se_dev->bus_bw = icc_get(geni_se_dev->dev, + geni_se_dev->vectors[0].src, + geni_se_dev->vectors[0].dst); + if (!(geni_se_dev->bus_bw)) { + GENI_SE_ERR(geni_se_dev->log_ctx, + false, NULL, + "%s: Error creating bus client (Core2x)\n", + __func__); + return -EFAULT; + } + geni_se_dev->bus_bw_noc = icc_get(geni_se_dev->dev, + geni_se_dev->vectors[1].src, + geni_se_dev->vectors[1].dst); + if (!(geni_se_dev->bus_bw_noc)) { + GENI_SE_ERR(geni_se_dev->log_ctx, + false, NULL, + "%s: Error creating bus client (DDR)\n", + __func__); + icc_put(geni_se_dev->bus_bw); + return -EFAULT; + } + } + + /* To reduce the higher ab values from individual drivers */ + rsc->ab = ab/2; + rsc->ib = ab; + rsc->ab_noc = 0; + rsc->ib_noc = ib; + INIT_LIST_HEAD(&rsc->ab_list_noc); + INIT_LIST_HEAD(&rsc->ib_list_noc); + } else { + if (unlikely(IS_ERR_OR_NULL(geni_se_dev->bus_bw))) { + geni_se_dev->bus_bw = icc_get(geni_se_dev->dev, + geni_se_dev->bus_mas_id, + geni_se_dev->bus_slv_id); + if (IS_ERR_OR_NULL(geni_se_dev->bus_bw)) { + GENI_SE_ERR(geni_se_dev->log_ctx, + false, NULL, + "%s: Error creating bus client\n", __func__); + return (int)PTR_ERR(geni_se_dev->bus_bw); + } + } + rsc->ab = ab; + rsc->ib = ib; + } + + INIT_LIST_HEAD(&rsc->ab_list); + INIT_LIST_HEAD(&rsc->ib_list); + + ret = of_property_read_string(geni_se_dev->dev->of_node, + "qcom,iommu-dma", &mode); + + if ((ret == 0) && (strcmp(mode, "disabled") == 0)) { + ret = geni_se_iommu_map_and_attach(geni_se_dev); + if (ret) + GENI_SE_ERR(geni_se_dev->log_ctx, false, NULL, + "%s: Error %d iommu_map_and_attach\n", + __func__, ret); + } + + return ret; +} +EXPORT_SYMBOL(geni_se_resources_init); + +/** + * geni_se_clk_tbl_get() - Get the clock table to program DFS + * @rsc: Resource for which the clock table is requested. + * @tbl: Table in which the output is returned. + * + * This function is called by the protocol drivers to determine the different + * clock frequencies supported by Serail Engine Core Clock. The protocol + * drivers use the output to determine the clock frequency index to be + * programmed into DFS. + * + * Return: number of valid performance levels in the table on success, + * standard Linux error codes on failure. + */ +int geni_se_clk_tbl_get(struct se_geni_rsc *rsc, unsigned long **tbl) +{ + struct geni_se_device *geni_se_dev; + int i; + unsigned long prev_freq = 0; + int ret = 0; + + if (unlikely(!rsc || !rsc->wrapper_dev || !rsc->se_clk || !tbl)) + return -EINVAL; + + geni_se_dev = dev_get_drvdata(rsc->wrapper_dev); + if (unlikely(!geni_se_dev)) + return -EPROBE_DEFER; + mutex_lock(&geni_se_dev->geni_dev_lock); + *tbl = NULL; + + if (geni_se_dev->clk_perf_tbl) { + *tbl = geni_se_dev->clk_perf_tbl; + ret = geni_se_dev->num_clk_levels; + goto exit_se_clk_tbl_get; + } + + geni_se_dev->clk_perf_tbl = kzalloc(sizeof(*geni_se_dev->clk_perf_tbl) * + MAX_CLK_PERF_LEVEL, GFP_KERNEL); + if (!geni_se_dev->clk_perf_tbl) { + ret = -ENOMEM; + goto exit_se_clk_tbl_get; + } + + for (i = 0; i < MAX_CLK_PERF_LEVEL; i++) { + geni_se_dev->clk_perf_tbl[i] = clk_round_rate(rsc->se_clk, + prev_freq + 1); + if (geni_se_dev->clk_perf_tbl[i] == prev_freq) { + geni_se_dev->clk_perf_tbl[i] = 0; + break; + } + prev_freq = geni_se_dev->clk_perf_tbl[i]; + } + geni_se_dev->num_clk_levels = i; + *tbl = geni_se_dev->clk_perf_tbl; + ret = geni_se_dev->num_clk_levels; +exit_se_clk_tbl_get: + mutex_unlock(&geni_se_dev->geni_dev_lock); + return ret; +} +EXPORT_SYMBOL(geni_se_clk_tbl_get); + +/** + * geni_se_clk_freq_match() - Get the matching or closest SE clock frequency + * @rsc: Resource for which the clock frequency is requested. + * @req_freq: Requested clock frequency. + * @index: Index of the resultant frequency in the table. + * @res_freq: Resultant frequency which matches or is closer to the + * requested frequency. + * @exact: Flag to indicate exact multiple requirement of the requested + * frequency . + * + * This function is called by the protocol drivers to determine the matching + * or closest frequency of the Serial Engine clock to be selected in order + * to meet the performance requirements. + * + * Return: 0 on success, standard Linux error codes on failure. + */ +int geni_se_clk_freq_match(struct se_geni_rsc *rsc, unsigned long req_freq, + unsigned int *index, unsigned long *res_freq, + bool exact) +{ + unsigned long *tbl; + int num_clk_levels; + int i; + unsigned long best_delta = 0; + unsigned long new_delta; + unsigned int divider; + + num_clk_levels = geni_se_clk_tbl_get(rsc, &tbl); + if (num_clk_levels < 0) + return num_clk_levels; + + if (num_clk_levels == 0) + return -EFAULT; + + *res_freq = 0; + + for (i = 0; i < num_clk_levels; i++) { + divider = DIV_ROUND_UP(tbl[i], req_freq); + new_delta = req_freq - (tbl[i] / divider); + + if (!best_delta || new_delta < best_delta) { + /* We have a new best! */ + *index = i; + *res_freq = tbl[i]; + + /*If the new best is exact then we're done*/ + if (new_delta == 0) + return 0; + + best_delta = new_delta; + } + } + + if (exact || !(*res_freq)) + return -ENOKEY; + + return 0; +} +EXPORT_SYMBOL(geni_se_clk_freq_match); + +/** + * geni_se_tx_dma_prep() - Prepare the Serial Engine for TX DMA transfer + * @wrapper_dev: QUPv3 Wrapper Device to which the TX buffer is mapped. + * @base: Base address of the SE register block. + * @tx_buf: Pointer to the TX buffer. + * @tx_len: Length of the TX buffer. + * @tx_dma: Pointer to store the mapped DMA address. + * + * This function is used to prepare the buffers for DMA TX. + * + * Return: 0 on success, standard Linux error codes on error/failure. + */ +int geni_se_tx_dma_prep(struct device *wrapper_dev, void __iomem *base, + void *tx_buf, int tx_len, dma_addr_t *tx_dma) +{ + int ret; + + if (unlikely(!wrapper_dev || !base || !tx_buf || !tx_len || !tx_dma)) + return -EINVAL; + + ret = geni_se_iommu_map_buf(wrapper_dev, tx_dma, tx_buf, tx_len, + DMA_TO_DEVICE); + if (ret) + return ret; + + geni_write_reg(7, base, SE_DMA_TX_IRQ_EN_SET); + geni_write_reg(GENI_SE_DMA_PTR_L(*tx_dma), base, SE_DMA_TX_PTR_L); + geni_write_reg(GENI_SE_DMA_PTR_H(*tx_dma), base, SE_DMA_TX_PTR_H); + geni_write_reg(1, base, SE_DMA_TX_ATTR); + geni_write_reg(tx_len, base, SE_DMA_TX_LEN); + return 0; +} +EXPORT_SYMBOL(geni_se_tx_dma_prep); + +/** + * geni_se_rx_dma_prep() - Prepare the Serial Engine for RX DMA transfer + * @wrapper_dev: QUPv3 Wrapper Device to which the RX buffer is mapped. + * @base: Base address of the SE register block. + * @rx_buf: Pointer to the RX buffer. + * @rx_len: Length of the RX buffer. + * @rx_dma: Pointer to store the mapped DMA address. + * + * This function is used to prepare the buffers for DMA RX. + * + * Return: 0 on success, standard Linux error codes on error/failure. + */ +int geni_se_rx_dma_prep(struct device *wrapper_dev, void __iomem *base, + void *rx_buf, int rx_len, dma_addr_t *rx_dma) +{ + int ret; + + if (unlikely(!wrapper_dev || !base || !rx_buf || !rx_len || !rx_dma)) + return -EINVAL; + + ret = geni_se_iommu_map_buf(wrapper_dev, rx_dma, rx_buf, rx_len, + DMA_FROM_DEVICE); + if (ret) + return ret; + + geni_write_reg(7, base, SE_DMA_RX_IRQ_EN_SET); + geni_write_reg(GENI_SE_DMA_PTR_L(*rx_dma), base, SE_DMA_RX_PTR_L); + geni_write_reg(GENI_SE_DMA_PTR_H(*rx_dma), base, SE_DMA_RX_PTR_H); + /* RX does not have EOT bit */ + geni_write_reg(0, base, SE_DMA_RX_ATTR); + geni_write_reg(rx_len, base, SE_DMA_RX_LEN); + return 0; +} +EXPORT_SYMBOL(geni_se_rx_dma_prep); + +/** + * geni_se_tx_dma_unprep() - Unprepare the Serial Engine after TX DMA transfer + * @wrapper_dev: QUPv3 Wrapper Device to which the RX buffer is mapped. + * @tx_dma: DMA address of the TX buffer. + * @tx_len: Length of the TX buffer. + * + * This function is used to unprepare the DMA buffers after DMA TX. + */ +void geni_se_tx_dma_unprep(struct device *wrapper_dev, + dma_addr_t tx_dma, int tx_len) +{ + if (tx_dma) + geni_se_iommu_unmap_buf(wrapper_dev, &tx_dma, tx_len, + DMA_TO_DEVICE); +} +EXPORT_SYMBOL(geni_se_tx_dma_unprep); + +/** + * geni_se_rx_dma_unprep() - Unprepare the Serial Engine after RX DMA transfer + * @wrapper_dev: QUPv3 Wrapper Device to which the RX buffer is mapped. + * @rx_dma: DMA address of the RX buffer. + * @rx_len: Length of the RX buffer. + * + * This function is used to unprepare the DMA buffers after DMA RX. + */ +void geni_se_rx_dma_unprep(struct device *wrapper_dev, + dma_addr_t rx_dma, int rx_len) +{ + if (rx_dma) + geni_se_iommu_unmap_buf(wrapper_dev, &rx_dma, rx_len, + DMA_FROM_DEVICE); +} +EXPORT_SYMBOL(geni_se_rx_dma_unprep); + +/** + * geni_se_qupv3_hw_version() - Read the QUPv3 Hardware version + * @wrapper_dev: Pointer to the corresponding QUPv3 wrapper core. + * @major: Buffer for Major Version field. + * @minor: Buffer for Minor Version field. + * @step: Buffer for Step Version field. + * + * Return: 0 on success, standard Linux error codes on failure/error. + */ +int geni_se_qupv3_hw_version(struct device *wrapper_dev, unsigned int *major, + unsigned int *minor, unsigned int *step) +{ + unsigned int version; + struct geni_se_device *geni_se_dev; + + if (!wrapper_dev || !major || !minor || !step) + return -EINVAL; + + geni_se_dev = dev_get_drvdata(wrapper_dev); + if (unlikely(!geni_se_dev)) + return -ENODEV; + + version = geni_read_reg(geni_se_dev->base, QUPV3_HW_VER); + *major = (version & HW_VER_MAJOR_MASK) >> HW_VER_MAJOR_SHFT; + *minor = (version & HW_VER_MINOR_MASK) >> HW_VER_MINOR_SHFT; + *step = version & HW_VER_STEP_MASK; + return 0; +} +EXPORT_SYMBOL(geni_se_qupv3_hw_version); + +static int geni_se_iommu_map_and_attach(struct geni_se_device *geni_se_dev) +{ + return 0; +} + +/** + * geni_se_iommu_map_buf() - Map a single buffer into QUPv3 context bank + * @wrapper_dev: Pointer to the corresponding QUPv3 wrapper core. + * @iova: Pointer in which the mapped virtual address is stored. + * @buf: Address of the buffer that needs to be mapped. + * @size: Size of the buffer. + * @dir: Direction of the DMA transfer. + * + * This function is used to map an already allocated buffer into the + * QUPv3 context bank device space. + * + * Return: 0 on success, standard Linux error codes on failure/error. + */ +int geni_se_iommu_map_buf(struct device *wrapper_dev, dma_addr_t *iova, + void *buf, size_t size, enum dma_data_direction dir) +{ + struct device *cb_dev; + struct geni_se_device *geni_se_dev; + + if (!wrapper_dev || !iova || !buf || !size) + return -EINVAL; + + *iova = DMA_ERROR_CODE; + geni_se_dev = dev_get_drvdata(wrapper_dev); + if (!geni_se_dev || !geni_se_dev->cb_dev) + return -ENODEV; + + cb_dev = geni_se_dev->cb_dev; + + *iova = dma_map_single(cb_dev, buf, size, dir); + if (dma_mapping_error(cb_dev, *iova)) + return -EIO; + return 0; +} +EXPORT_SYMBOL(geni_se_iommu_map_buf); + +/** + * geni_se_iommu_alloc_buf() - Allocate & map a single buffer into QUPv3 + * context bank + * @wrapper_dev: Pointer to the corresponding QUPv3 wrapper core. + * @iova: Pointer in which the mapped virtual address is stored. + * @size: Size of the buffer. + * + * This function is used to allocate a buffer and map it into the + * QUPv3 context bank device space. + * + * Return: address of the buffer on success, NULL or ERR_PTR on + * failure/error. + */ +void *geni_se_iommu_alloc_buf(struct device *wrapper_dev, dma_addr_t *iova, + size_t size) +{ + struct device *cb_dev; + struct geni_se_device *geni_se_dev; + void *buf = NULL; + + if (!wrapper_dev || !iova || !size) + return ERR_PTR(-EINVAL); + + *iova = DMA_ERROR_CODE; + geni_se_dev = dev_get_drvdata(wrapper_dev); + if (!geni_se_dev || !geni_se_dev->cb_dev) + return ERR_PTR(-ENODEV); + + cb_dev = geni_se_dev->cb_dev; + + buf = dma_alloc_coherent(cb_dev, size, iova, GFP_KERNEL); + if (!buf) + GENI_SE_ERR(geni_se_dev->log_ctx, false, NULL, + "%s: Failed dma_alloc_coherent\n", __func__); + return buf; +} +EXPORT_SYMBOL(geni_se_iommu_alloc_buf); + +/** + * geni_se_iommu_unmap_buf() - Unmap a single buffer from QUPv3 context bank + * @wrapper_dev: Pointer to the corresponding QUPv3 wrapper core. + * @iova: Pointer in which the mapped virtual address is stored. + * @size: Size of the buffer. + * @dir: Direction of the DMA transfer. + * + * This function is used to unmap an already mapped buffer from the + * QUPv3 context bank device space. + * + * Return: 0 on success, standard Linux error codes on failure/error. + */ +int geni_se_iommu_unmap_buf(struct device *wrapper_dev, dma_addr_t *iova, + size_t size, enum dma_data_direction dir) +{ + struct device *cb_dev; + struct geni_se_device *geni_se_dev; + + if (!wrapper_dev || !iova || !size) + return -EINVAL; + + geni_se_dev = dev_get_drvdata(wrapper_dev); + if (!geni_se_dev || !geni_se_dev->cb_dev) + return -ENODEV; + + cb_dev = geni_se_dev->cb_dev; + + dma_unmap_single(cb_dev, *iova, size, dir); + return 0; +} +EXPORT_SYMBOL(geni_se_iommu_unmap_buf); + +/** + * geni_se_iommu_free_buf() - Unmap & free a single buffer from QUPv3 + * context bank + * @wrapper_dev: Pointer to the corresponding QUPv3 wrapper core. + * @iova: Pointer in which the mapped virtual address is stored. + * @buf: Address of the buffer. + * @size: Size of the buffer. + * + * This function is used to unmap and free a buffer from the + * QUPv3 context bank device space. + * + * Return: 0 on success, standard Linux error codes on failure/error. + */ +int geni_se_iommu_free_buf(struct device *wrapper_dev, dma_addr_t *iova, + void *buf, size_t size) +{ + struct device *cb_dev; + struct geni_se_device *geni_se_dev; + + if (!wrapper_dev || !iova || !buf || !size) + return -EINVAL; + + geni_se_dev = dev_get_drvdata(wrapper_dev); + if (!geni_se_dev || !geni_se_dev->cb_dev) + return -ENODEV; + + cb_dev = geni_se_dev->cb_dev; + + dma_free_coherent(cb_dev, size, buf, *iova); + return 0; +} +EXPORT_SYMBOL(geni_se_iommu_free_buf); + +/** + * geni_se_dump_dbg_regs() - Print relevant registers that capture most + * accurately the state of an SE. + * @_dev: Pointer to the SE's device. + * @iomem: Base address of the SE's register space. + * @ipc: IPC log context handle. + * + * This function is used to print out all the registers that capture the state + * of an SE to help debug any errors. + * + * Return: None + */ +void geni_se_dump_dbg_regs(struct se_geni_rsc *rsc, void __iomem *base, + void *ipc) +{ + u32 m_cmd0 = 0; + u32 m_irq_status = 0; + u32 geni_status = 0; + u32 geni_ios = 0; + u32 dma_rx_irq = 0; + u32 dma_tx_irq = 0; + u32 rx_fifo_status = 0; + u32 tx_fifo_status = 0; + u32 se_dma_dbg = 0; + u32 m_cmd_ctrl = 0; + u32 se_dma_rx_len = 0; + u32 se_dma_rx_len_in = 0; + u32 se_dma_tx_len = 0; + u32 se_dma_tx_len_in = 0; + struct geni_se_device *geni_se_dev; + + if (!ipc) + return; + + geni_se_dev = dev_get_drvdata(rsc->wrapper_dev); + if (unlikely(!geni_se_dev || !geni_se_dev->bus_bw)) + return; + if (unlikely(list_empty(&rsc->ab_list) || list_empty(&rsc->ib_list))) { + GENI_SE_DBG(ipc, false, NULL, "%s: Clocks not on\n", __func__); + return; + } + m_cmd0 = geni_read_reg(base, SE_GENI_M_CMD0); + m_irq_status = geni_read_reg(base, SE_GENI_M_IRQ_STATUS); + geni_status = geni_read_reg(base, SE_GENI_STATUS); + geni_ios = geni_read_reg(base, SE_GENI_IOS); + dma_rx_irq = geni_read_reg(base, SE_DMA_TX_IRQ_STAT); + dma_tx_irq = geni_read_reg(base, SE_DMA_RX_IRQ_STAT); + rx_fifo_status = geni_read_reg(base, SE_GENI_RX_FIFO_STATUS); + tx_fifo_status = geni_read_reg(base, SE_GENI_TX_FIFO_STATUS); + se_dma_dbg = geni_read_reg(base, SE_DMA_DEBUG_REG0); + m_cmd_ctrl = geni_read_reg(base, SE_GENI_M_CMD_CTRL_REG); + se_dma_rx_len = geni_read_reg(base, SE_DMA_RX_LEN); + se_dma_rx_len_in = geni_read_reg(base, SE_DMA_RX_LEN_IN); + se_dma_tx_len = geni_read_reg(base, SE_DMA_TX_LEN); + se_dma_tx_len_in = geni_read_reg(base, SE_DMA_TX_LEN_IN); + + GENI_SE_DBG(ipc, false, NULL, + "%s: m_cmd0:0x%x, m_irq_status:0x%x, geni_status:0x%x, geni_ios:0x%x\n", + __func__, m_cmd0, m_irq_status, geni_status, geni_ios); + GENI_SE_DBG(ipc, false, NULL, + "dma_rx_irq:0x%x, dma_tx_irq:0x%x, rx_fifo_sts:0x%x, tx_fifo_sts:0x%x\n" + , dma_rx_irq, dma_tx_irq, rx_fifo_status, tx_fifo_status); + GENI_SE_DBG(ipc, false, NULL, + "se_dma_dbg:0x%x, m_cmd_ctrl:0x%x, dma_rxlen:0x%x, dma_rxlen_in:0x%x\n", + se_dma_dbg, m_cmd_ctrl, se_dma_rx_len, se_dma_rx_len_in); + GENI_SE_DBG(ipc, false, NULL, + "dma_txlen:0x%x, dma_txlen_in:0x%x\n", se_dma_tx_len, se_dma_tx_len_in); +} +EXPORT_SYMBOL(geni_se_dump_dbg_regs); + +static const struct of_device_id geni_se_dt_match[] = { + { .compatible = "qcom,qupv3-geni-se", }, + { .compatible = "qcom,qupv3-geni-se-cb", }, + {} +}; + +static struct bus_vectors *get_icc_paths(struct platform_device *pdev, + struct geni_se_device *host) +{ + struct device *dev = &pdev->dev; + int i = 0, len; + bool mem_err = false; + const uint32_t *vec_arr = NULL; + struct bus_vectors *vectors = NULL; + + vec_arr = of_get_property(dev->of_node, + "qcom,msm-bus,vectors-bus-ids", &len); + if (vec_arr == NULL) { + pr_err("Error: Vector array not found\n"); + goto out; + } + + if (len != host->num_paths * sizeof(uint32_t) * 2) { + pr_err("Error: Length-error on getting vectors\n"); + goto out; + } + + vectors = devm_kzalloc(dev, host->num_paths * + sizeof(struct bus_vectors), GFP_KERNEL); + if (!vectors) { + mem_err = true; + goto out; + } + + for (i = 0; i < host->num_paths; i++) { + vectors[i].src = + be32_to_cpu(vec_arr[(i*2)]); + vectors[i].dst = + be32_to_cpu(vec_arr[(i*2) + 1]); + } + + return vectors; +out: + return NULL; +} + +static int geni_se_iommu_probe(struct device *dev) +{ + struct geni_se_device *geni_se_dev; + + geni_se_dev = dev_get_drvdata(dev->parent); + geni_se_dev->cb_dev = dev; + + GENI_SE_DBG(geni_se_dev->log_ctx, false, NULL, + "%s: Probe successful\n", __func__); + return 0; +} + +static int geni_se_probe(struct platform_device *pdev) +{ + int ret; + struct device *dev = &pdev->dev; + struct resource *res; + struct geni_se_device *geni_se_dev; + + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + if (ret) { + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(&pdev->dev, "could not set DMA mask\n"); + return ret; + } + } + + if (of_device_is_compatible(dev->of_node, "qcom,qupv3-geni-se-cb")) + return geni_se_iommu_probe(dev); + + geni_se_dev = devm_kzalloc(dev, sizeof(*geni_se_dev), GFP_KERNEL); + if (!geni_se_dev) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "%s: Mandatory resource info not found\n", + __func__); + return -EINVAL; + } + + geni_se_dev->base = devm_ioremap_resource(dev, res); + if (IS_ERR(geni_se_dev->base)) { + dev_err(dev, "%s: Error mapping the resource\n", __func__); + return PTR_ERR(geni_se_dev->base); + } + + geni_se_dev->dev = dev; + geni_se_dev->cb_dev = dev; + ret = of_property_read_u32(dev->of_node, "qcom,msm-bus,num-paths", + &geni_se_dev->num_paths); + if (!ret) { + geni_se_dev->update = 0; + geni_se_dev->vectors = get_icc_paths(pdev, geni_se_dev); + if (geni_se_dev->vectors == NULL) { + dev_err(dev, + "%s: Error missing bus master and slave id\n", + __func__); + return -EINVAL; + } + } else { + geni_se_dev->num_paths = 1; + ret = of_property_read_u32(dev->of_node, "qcom,bus-mas-id", + &geni_se_dev->bus_mas_id); + if (ret) { + dev_err(dev, "%s: Error missing bus master id\n", + __func__); + return -EINVAL; + } + ret = of_property_read_u32(dev->of_node, "qcom,bus-slv-id", + &geni_se_dev->bus_slv_id); + if (ret) { + dev_err(dev, "%s: Error missing bus slave id\n", + __func__); + return -EINVAL; + } + } + + geni_se_dev->vote_for_bw = of_property_read_bool(dev->of_node, + "qcom,vote-for-bw"); + geni_se_dev->iommu_s1_bypass = of_property_read_bool(dev->of_node, + "qcom,iommu-s1-bypass"); + geni_se_dev->bus_bw_set = default_bus_bw_set; + geni_se_dev->bus_bw_set_size = + ARRAY_SIZE(default_bus_bw_set); + if (geni_se_dev->num_paths == 2) { + geni_se_dev->bus_bw_set_noc = default_bus_bw_set; + geni_se_dev->bus_bw_set_size_noc = + ARRAY_SIZE(default_bus_bw_set); + } + mutex_init(&geni_se_dev->iommu_lock); + INIT_LIST_HEAD(&geni_se_dev->ab_list_head); + INIT_LIST_HEAD(&geni_se_dev->ib_list_head); + if (geni_se_dev->num_paths == 2) { + INIT_LIST_HEAD(&geni_se_dev->ab_list_head_noc); + INIT_LIST_HEAD(&geni_se_dev->ib_list_head_noc); + } + mutex_init(&geni_se_dev->geni_dev_lock); + geni_se_dev->log_ctx = ipc_log_context_create(NUM_LOG_PAGES, + dev_name(geni_se_dev->dev), 0); + if (!geni_se_dev->log_ctx) + dev_err(dev, "%s Failed to allocate log context\n", __func__); + + dev_set_drvdata(dev, geni_se_dev); + + ret = of_platform_populate(dev->of_node, geni_se_dt_match, NULL, dev); + if (ret) { + dev_err(dev, "%s: Error populating children\n", __func__); + ipc_log_context_destroy(geni_se_dev->log_ctx); + return ret; + } + + GENI_SE_DBG(geni_se_dev->log_ctx, false, NULL, + "%s: Probe successful\n", __func__); + return 0; +} + +static int geni_se_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct geni_se_device *geni_se_dev = dev_get_drvdata(dev); + + ipc_log_context_destroy(geni_se_dev->log_ctx); + return 0; +} + +static struct platform_driver geni_se_driver = { + .driver = { + .name = "qupv3_geni_se", + .of_match_table = geni_se_dt_match, + }, + .probe = geni_se_probe, + .remove = geni_se_remove, +}; + +static int __init geni_se_driver_init(void) +{ + return platform_driver_register(&geni_se_driver); +} +arch_initcall(geni_se_driver_init); + +static void __exit geni_se_driver_exit(void) +{ + platform_driver_unregister(&geni_se_driver); +} +module_exit(geni_se_driver_exit); + +MODULE_DESCRIPTION("GENI Serial Engine Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 3083dbae35f7..3e15075c69d5 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -1010,20 +1010,24 @@ config SERIAL_MSM_CONSOLE select SERIAL_CORE_CONSOLE select SERIAL_EARLYCON -config SERIAL_QCOM_GENI - tristate "QCOM on-chip GENI based serial port support" - depends on ARCH_QCOM || COMPILE_TEST - depends on QCOM_GENI_SE +config SERIAL_MSM_GENI + tristate "MSM on-chip GENI HW based serial port support" + depends on ARCH_QCOM + depends on MSM_GENI_SE select SERIAL_CORE + help + Serial driver for Qualcomm Technologies Inc's GENI based QUPv3 + hardware. + The driver supports console and High speed UART functions. -config SERIAL_QCOM_GENI_CONSOLE - bool "QCOM GENI Serial Console support" - depends on SERIAL_QCOM_GENI=y +config SERIAL_MSM_GENI_CONSOLE + bool "MSM on-chip GENI HW based console support" + depends on SERIAL_MSM_GENI=y select SERIAL_CORE_CONSOLE select SERIAL_EARLYCON help - Serial console driver for Qualcomm Technologies Inc's GENI based - QUP hardware. + Serial console driver for Qualcomm Technologies Inc's GENI based + QUP hardware. config SERIAL_VT8500 bool "VIA VT8500 on-chip serial port support" diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index 15a0fccadf7e..f2b503b9592b 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -58,7 +58,7 @@ obj-$(CONFIG_SERIAL_SGI_IOC3) += ioc3_serial.o obj-$(CONFIG_SERIAL_ATMEL) += atmel_serial.o obj-$(CONFIG_SERIAL_UARTLITE) += uartlite.o obj-$(CONFIG_SERIAL_MSM) += msm_serial.o -obj-$(CONFIG_SERIAL_QCOM_GENI) += qcom_geni_serial.o +obj-$(CONFIG_SERIAL_MSM_GENI) += msm_geni_serial.o obj-$(CONFIG_SERIAL_KS8695) += serial_ks8695.o obj-$(CONFIG_SERIAL_OMAP) += omap-serial.o obj-$(CONFIG_SERIAL_ALTERA_UART) += altera_uart.o diff --git a/drivers/tty/serial/msm_geni_serial.c b/drivers/tty/serial/msm_geni_serial.c new file mode 100644 index 000000000000..4299ab3cf879 --- /dev/null +++ b/drivers/tty/serial/msm_geni_serial.c @@ -0,0 +1,2777 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016-2019, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* UART specific GENI registers */ +#define SE_UART_LOOPBACK_CFG (0x22C) +#define SE_UART_TX_TRANS_CFG (0x25C) +#define SE_UART_TX_WORD_LEN (0x268) +#define SE_UART_TX_STOP_BIT_LEN (0x26C) +#define SE_UART_TX_TRANS_LEN (0x270) +#define SE_UART_RX_TRANS_CFG (0x280) +#define SE_UART_RX_WORD_LEN (0x28C) +#define SE_UART_RX_STALE_CNT (0x294) +#define SE_UART_TX_PARITY_CFG (0x2A4) +#define SE_UART_RX_PARITY_CFG (0x2A8) +#define SE_UART_MANUAL_RFR (0x2AC) + +/* SE_UART_LOOPBACK_CFG */ +#define NO_LOOPBACK (0) +#define TX_RX_LOOPBACK (0x1) +#define CTS_RFR_LOOPBACK (0x2) +#define CTSRFR_TXRX_LOOPBACK (0x3) + +/* SE_UART_TRANS_CFG */ +#define UART_TX_PAR_EN (BIT(0)) +#define UART_CTS_MASK (BIT(1)) + +/* SE_UART_TX_WORD_LEN */ +#define TX_WORD_LEN_MSK (GENMASK(9, 0)) + +/* SE_UART_TX_STOP_BIT_LEN */ +#define TX_STOP_BIT_LEN_MSK (GENMASK(23, 0)) +#define TX_STOP_BIT_LEN_1 (0) +#define TX_STOP_BIT_LEN_1_5 (1) +#define TX_STOP_BIT_LEN_2 (2) + +/* SE_UART_TX_TRANS_LEN */ +#define TX_TRANS_LEN_MSK (GENMASK(23, 0)) + +/* SE_UART_RX_TRANS_CFG */ +#define UART_RX_INS_STATUS_BIT (BIT(2)) +#define UART_RX_PAR_EN (BIT(3)) + +/* SE_UART_RX_WORD_LEN */ +#define RX_WORD_LEN_MASK (GENMASK(9, 0)) + +/* SE_UART_RX_STALE_CNT */ +#define RX_STALE_CNT (GENMASK(23, 0)) + +/* SE_UART_TX_PARITY_CFG/RX_PARITY_CFG */ +#define PAR_CALC_EN (BIT(0)) +#define PAR_MODE_MSK (GENMASK(2, 1)) +#define PAR_MODE_SHFT (1) +#define PAR_EVEN (0x00) +#define PAR_ODD (0x01) +#define PAR_SPACE (0x10) +#define PAR_MARK (0x11) + +/* SE_UART_MANUAL_RFR register fields */ +#define UART_MANUAL_RFR_EN (BIT(31)) +#define UART_RFR_NOT_READY (BIT(1)) +#define UART_RFR_READY (BIT(0)) + +/* UART M_CMD OP codes */ +#define UART_START_TX (0x1) +#define UART_START_BREAK (0x4) +#define UART_STOP_BREAK (0x5) +/* UART S_CMD OP codes */ +#define UART_START_READ (0x1) +#define UART_PARAM (0x1) +#define UART_PARAM_RFR_OPEN (BIT(7)) + +/* UART DMA Rx GP_IRQ_BITS */ +#define UART_DMA_RX_PARITY_ERR BIT(5) +#define UART_DMA_RX_ERRS (GENMASK(5, 6)) +#define UART_DMA_RX_BREAK (GENMASK(7, 8)) + +#define UART_OVERSAMPLING (32) +#define STALE_TIMEOUT (16) +#define DEFAULT_BITS_PER_CHAR (10) +#define GENI_UART_NR_PORTS (15) +#define GENI_UART_CONS_PORTS (1) +#define DEF_FIFO_DEPTH_WORDS (16) +#define DEF_TX_WM (2) +#define DEF_FIFO_WIDTH_BITS (32) +#define UART_CORE2X_VOTE (5000) +#define UART_CONSOLE_CORE2X_VOTE (960) + +#define WAKEBYTE_TIMEOUT_MSEC (2000) +#define WAIT_XFER_MAX_ITER (50) +#define WAIT_XFER_MAX_TIMEOUT_US (10000) +#define WAIT_XFER_MIN_TIMEOUT_US (9000) +#define IPC_LOG_PWR_PAGES (6) +#define IPC_LOG_MISC_PAGES (10) +#define IPC_LOG_TX_RX_PAGES (8) +#define DATA_BYTES_PER_LINE (32) + +#define IPC_LOG_MSG(ctx, x...) ipc_log_string(ctx, x) + +#define DMA_RX_BUF_SIZE (2048) +#define UART_CONSOLE_RX_WM (2) +struct msm_geni_serial_port { + struct uart_port uport; + char name[20]; + unsigned int tx_fifo_depth; + unsigned int tx_fifo_width; + unsigned int rx_fifo_depth; + unsigned int tx_wm; + unsigned int rx_wm; + unsigned int rx_rfr; + int xfer_mode; + struct dentry *dbg; + bool port_setup; + unsigned int *rx_fifo; + int (*handle_rx)(struct uart_port *uport, + unsigned int rx_fifo_wc, + unsigned int rx_last_byte_valid, + unsigned int rx_last, + bool drop_rx); + struct device *wrapper_dev; + struct se_geni_rsc serial_rsc; + dma_addr_t tx_dma; + unsigned int xmit_size; + void *rx_buf; + dma_addr_t rx_dma; + int loopback; + int wakeup_irq; + unsigned char wakeup_byte; + struct wakeup_source geni_wake; + void *ipc_log_tx; + void *ipc_log_rx; + void *ipc_log_pwr; + void *ipc_log_misc; + unsigned int cur_baud; + int ioctl_count; + int edge_count; + bool manual_flow; +}; + +static const struct uart_ops msm_geni_serial_pops; +static struct uart_driver msm_geni_console_driver; +static struct uart_driver msm_geni_serial_hs_driver; +static int handle_rx_console(struct uart_port *uport, + unsigned int rx_fifo_wc, + unsigned int rx_last_byte_valid, + unsigned int rx_last, + bool drop_rx); +static int handle_rx_hs(struct uart_port *uport, + unsigned int rx_fifo_wc, + unsigned int rx_last_byte_valid, + unsigned int rx_last, + bool drop_rx); +static unsigned int msm_geni_serial_tx_empty(struct uart_port *port); +static int msm_geni_serial_power_on(struct uart_port *uport); +static void msm_geni_serial_power_off(struct uart_port *uport); +static int msm_geni_serial_poll_bit(struct uart_port *uport, + int offset, int bit_field, bool set); +static void msm_geni_serial_stop_rx(struct uart_port *uport); +static int msm_geni_serial_runtime_resume(struct device *dev); +static int msm_geni_serial_runtime_suspend(struct device *dev); + +static atomic_t uart_line_id = ATOMIC_INIT(0); + +#define GET_DEV_PORT(uport) \ + container_of(uport, struct msm_geni_serial_port, uport) + +static struct msm_geni_serial_port msm_geni_console_port; +static struct msm_geni_serial_port msm_geni_serial_ports[GENI_UART_NR_PORTS]; + +static void msm_geni_serial_config_port(struct uart_port *uport, int cfg_flags) +{ + if (cfg_flags & UART_CONFIG_TYPE) + uport->type = PORT_MSM; +} + +static ssize_t loopback_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct msm_geni_serial_port *port = platform_get_drvdata(pdev); + + return scnprintf(buf, sizeof(int), "%d\n", port->loopback); +} + +static ssize_t loopback_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t size) +{ + struct platform_device *pdev = to_platform_device(dev); + struct msm_geni_serial_port *port = platform_get_drvdata(pdev); + + if (kstrtoint(buf, 0, &port->loopback)) { + dev_err(dev, "Invalid input\n"); + return -EINVAL; + } + return size; +} + +static DEVICE_ATTR_RW(loopback); + +static void dump_ipc(void *ipc_ctx, char *prefix, char *string, + u64 addr, int size) + +{ + char buf[DATA_BYTES_PER_LINE * 2]; + int len = 0; + + len = min(size, DATA_BYTES_PER_LINE); + hex_dump_to_buffer(string, len, DATA_BYTES_PER_LINE, 1, buf, + sizeof(buf), false); + ipc_log_string(ipc_ctx, "%s[0x%.10x:%d] : %s", prefix, + (unsigned int)addr, size, buf); +} + +static bool device_pending_suspend(struct uart_port *uport) +{ + int usage_count = atomic_read(&uport->dev->power.usage_count); + + return (pm_runtime_status_suspended(uport->dev) || !usage_count); +} + +static bool check_transfers_inflight(struct uart_port *uport) +{ + bool xfer_on = false; + bool tx_active = false; + bool tx_fifo_status = false; + bool m_cmd_active = false; + bool rx_active = false; + u32 rx_fifo_status = 0; + struct msm_geni_serial_port *port = GET_DEV_PORT(uport); + u32 geni_status = geni_read_reg_nolog(uport->membase, + SE_GENI_STATUS); + struct circ_buf *xmit = &uport->state->xmit; + + /* Possible stop tx is called multiple times. */ + m_cmd_active = geni_status & M_GENI_CMD_ACTIVE; + if (port->xfer_mode == SE_DMA) { + tx_fifo_status = port->tx_dma ? 1 : 0; + rx_fifo_status = + geni_read_reg_nolog(uport->membase, SE_DMA_RX_LEN_IN); + } else { + tx_fifo_status = geni_read_reg_nolog(uport->membase, + SE_GENI_TX_FIFO_STATUS); + rx_fifo_status = geni_read_reg_nolog(uport->membase, + SE_GENI_RX_FIFO_STATUS); + } + tx_active = m_cmd_active || tx_fifo_status; + rx_active = rx_fifo_status ? true : false; + + if (rx_active || tx_active || !uart_circ_empty(xmit)) + xfer_on = true; + + return xfer_on; +} + +static void wait_for_transfers_inflight(struct uart_port *uport) +{ + int iter = 0; + struct msm_geni_serial_port *port = GET_DEV_PORT(uport); + + while (iter < WAIT_XFER_MAX_ITER) { + if (check_transfers_inflight(uport)) { + usleep_range(WAIT_XFER_MIN_TIMEOUT_US, + WAIT_XFER_MAX_TIMEOUT_US); + iter++; + } else { + break; + } + } + if (check_transfers_inflight(uport)) { + u32 geni_status = geni_read_reg_nolog(uport->membase, + SE_GENI_STATUS); + u32 geni_ios = geni_read_reg_nolog(uport->membase, SE_GENI_IOS); + u32 rx_fifo_status = geni_read_reg_nolog(uport->membase, + SE_GENI_RX_FIFO_STATUS); + u32 rx_dma = + geni_read_reg_nolog(uport->membase, SE_DMA_RX_LEN_IN); + + IPC_LOG_MSG(port->ipc_log_misc, + "%s IOS 0x%x geni status 0x%x rx: fifo 0x%x dma 0x%x\n", + __func__, geni_ios, geni_status, rx_fifo_status, rx_dma); + } +} + +static int vote_clock_on(struct uart_port *uport) +{ + struct msm_geni_serial_port *port = GET_DEV_PORT(uport); + int usage_count = atomic_read(&uport->dev->power.usage_count); + int ret = 0; + + ret = msm_geni_serial_power_on(uport); + if (ret) { + dev_err(uport->dev, "Failed to vote clock on\n"); + return ret; + } + port->ioctl_count++; + IPC_LOG_MSG(port->ipc_log_pwr, "%s%s ioctl %d usage_count %d\n", + __func__, current->comm, port->ioctl_count, usage_count); + return 0; +} + +static int vote_clock_off(struct uart_port *uport) +{ + struct msm_geni_serial_port *port = GET_DEV_PORT(uport); + int usage_count = atomic_read(&uport->dev->power.usage_count); + + if (!pm_runtime_enabled(uport->dev)) { + dev_err(uport->dev, "RPM not available.Can't enable clocks\n"); + return -EPERM; + } + if (!port->ioctl_count) { + dev_warn(uport->dev, "%s:Imbalanced vote off ioctl %d\n", + __func__, port->ioctl_count); + IPC_LOG_MSG(port->ipc_log_pwr, + "%s:Imbalanced vote_off from userspace. %d", + __func__, port->ioctl_count); + return -EPERM; + } + wait_for_transfers_inflight(uport); + port->ioctl_count--; + msm_geni_serial_power_off(uport); + IPC_LOG_MSG(port->ipc_log_pwr, "%s%s ioctl %d usage_count %d\n", + __func__, current->comm, port->ioctl_count, usage_count); + return 0; +}; + +static int msm_geni_serial_ioctl(struct uart_port *uport, unsigned int cmd, + unsigned long arg) +{ + int ret = -ENOIOCTLCMD; + + switch (cmd) { + case TIOCPMGET: { + ret = vote_clock_on(uport); + break; + } + case TIOCPMPUT: { + ret = vote_clock_off(uport); + break; + } + case TIOCPMACT: { + ret = !pm_runtime_status_suspended(uport->dev); + break; + } + default: + break; + } + return ret; +} + +static void msm_geni_serial_break_ctl(struct uart_port *uport, int ctl) +{ + struct msm_geni_serial_port *port = GET_DEV_PORT(uport); + + if (!uart_console(uport) && device_pending_suspend(uport)) { + IPC_LOG_MSG(port->ipc_log_misc, + "%s.Device is suspended.\n", __func__); + return; + } + + if (ctl) { + wait_for_transfers_inflight(uport); + geni_setup_m_cmd(uport->membase, UART_START_BREAK, 0); + } else { + geni_setup_m_cmd(uport->membase, UART_STOP_BREAK, 0); + } + /* Ensure break start/stop command is setup before returning.*/ + mb(); +} + +static unsigned int msm_geni_cons_get_mctrl(struct uart_port *uport) +{ + return TIOCM_DSR | TIOCM_CAR | TIOCM_CTS; +} + +static unsigned int msm_geni_serial_get_mctrl(struct uart_port *uport) +{ + u32 geni_ios = 0; + unsigned int mctrl = TIOCM_DSR | TIOCM_CAR; + + if (device_pending_suspend(uport)) + return TIOCM_DSR | TIOCM_CAR | TIOCM_CTS; + + geni_ios = geni_read_reg_nolog(uport->membase, SE_GENI_IOS); + if (!(geni_ios & IO2_DATA_IN)) + mctrl |= TIOCM_CTS; + + return mctrl; +} + +static void msm_geni_cons_set_mctrl(struct uart_port *uport, + unsigned int mctrl) +{ +} + +static void msm_geni_serial_set_mctrl(struct uart_port *uport, + unsigned int mctrl) +{ + u32 uart_manual_rfr = 0; + struct msm_geni_serial_port *port = GET_DEV_PORT(uport); + + if (device_pending_suspend(uport)) { + IPC_LOG_MSG(port->ipc_log_misc, + "%s.Device is suspended.\n", __func__); + return; + } + if (!(mctrl & TIOCM_RTS)) { + uart_manual_rfr |= (UART_MANUAL_RFR_EN | UART_RFR_NOT_READY); + port->manual_flow = true; + } else { + port->manual_flow = false; + } + geni_write_reg_nolog(uart_manual_rfr, uport->membase, + SE_UART_MANUAL_RFR); + /* Write to flow control must complete before return to client*/ + mb(); +} + +static const char *msm_geni_serial_get_type(struct uart_port *uport) +{ + return "MSM"; +} + +static struct msm_geni_serial_port *get_port_from_line(int line, + bool is_console) +{ + struct msm_geni_serial_port *port = NULL; + + if (is_console) { + if ((line < 0) || (line >= GENI_UART_CONS_PORTS)) + port = ERR_PTR(-ENXIO); + port = &msm_geni_console_port; + } else { + if ((line < 0) || (line >= GENI_UART_NR_PORTS)) + return ERR_PTR(-ENXIO); + port = &msm_geni_serial_ports[line]; + } + + return port; +} + +static int msm_geni_serial_power_on(struct uart_port *uport) +{ + int ret = 0; + struct msm_geni_serial_port *port = GET_DEV_PORT(uport); + + if (!pm_runtime_enabled(uport->dev)) { + if (pm_runtime_status_suspended(uport->dev)) { + struct uart_state *state = uport->state; + struct tty_port *tport = &state->port; + int lock = mutex_trylock(&tport->mutex); + + IPC_LOG_MSG(port->ipc_log_pwr, + "%s:Manual resume\n", __func__); + pm_runtime_disable(uport->dev); + ret = msm_geni_serial_runtime_resume(uport->dev); + if (ret) { + IPC_LOG_MSG(port->ipc_log_pwr, + "%s:Manual RPM CB failed %d\n", + __func__, ret); + } else { + pm_runtime_get_noresume(uport->dev); + pm_runtime_set_active(uport->dev); + enable_irq(uport->irq); + } + pm_runtime_enable(uport->dev); + if (lock) + mutex_unlock(&tport->mutex); + } + } else { + ret = pm_runtime_get_sync(uport->dev); + if (ret < 0) { + IPC_LOG_MSG(port->ipc_log_pwr, "%s Err\n", __func__); + WARN_ON_ONCE(1); + pm_runtime_put_noidle(uport->dev); + pm_runtime_set_suspended(uport->dev); + return ret; + } + } + return 0; +} + +static void msm_geni_serial_power_off(struct uart_port *uport) +{ + struct msm_geni_serial_port *port = GET_DEV_PORT(uport); + int usage_count = atomic_read(&uport->dev->power.usage_count); + + if (!usage_count) { + IPC_LOG_MSG(port->ipc_log_pwr, "%s: Usage Count is already 0\n", + __func__); + return; + } + pm_runtime_mark_last_busy(uport->dev); + pm_runtime_put_autosuspend(uport->dev); +} + +static int msm_geni_serial_poll_bit(struct uart_port *uport, + int offset, int bit_field, bool set) +{ + int iter = 0; + unsigned int reg; + bool met = false; + struct msm_geni_serial_port *port = NULL; + bool cond = false; + unsigned int baud = 115200; + unsigned int fifo_bits = DEF_FIFO_DEPTH_WORDS * DEF_FIFO_WIDTH_BITS; + unsigned long total_iter = 1000; + + + if (uport->private_data && !uart_console(uport)) { + port = GET_DEV_PORT(uport); + baud = (port->cur_baud ? port->cur_baud : 115200); + fifo_bits = port->tx_fifo_depth * port->tx_fifo_width; + /* + * Total polling iterations based on FIFO worth of bytes to be + * sent at current baud .Add a little fluff to the wait. + */ + total_iter = ((fifo_bits * USEC_PER_SEC) / baud) / 10; + total_iter += 50; + } + + while (iter < total_iter) { + reg = geni_read_reg_nolog(uport->membase, offset); + cond = reg & bit_field; + if (cond == set) { + met = true; + break; + } + udelay(10); + iter++; + } + return met; +} + +static void msm_geni_serial_setup_tx(struct uart_port *uport, + unsigned int xmit_size) +{ + u32 m_cmd = 0; + + geni_write_reg_nolog(xmit_size, uport->membase, SE_UART_TX_TRANS_LEN); + m_cmd |= (UART_START_TX << M_OPCODE_SHFT); + geni_write_reg_nolog(m_cmd, uport->membase, SE_GENI_M_CMD0); + /* + * Writes to enable the primary sequencer should go through before + * exiting this function. + */ + mb(); +} + +static void msm_geni_serial_poll_cancel_tx(struct uart_port *uport) +{ + int done = 0; + unsigned int irq_clear = M_CMD_DONE_EN; + + done = msm_geni_serial_poll_bit(uport, SE_GENI_M_IRQ_STATUS, + M_CMD_DONE_EN, true); + if (!done) { + geni_write_reg_nolog(M_GENI_CMD_ABORT, uport->membase, + SE_GENI_M_CMD_CTRL_REG); + irq_clear |= M_CMD_ABORT_EN; + msm_geni_serial_poll_bit(uport, SE_GENI_M_IRQ_STATUS, + M_CMD_ABORT_EN, true); + } + geni_write_reg_nolog(irq_clear, uport->membase, SE_GENI_M_IRQ_CLEAR); +} + +static void msm_geni_serial_abort_rx(struct uart_port *uport) +{ + unsigned int irq_clear = S_CMD_DONE_EN; + + geni_abort_s_cmd(uport->membase); + /* Ensure this goes through before polling. */ + mb(); + irq_clear |= S_CMD_ABORT_EN; + msm_geni_serial_poll_bit(uport, SE_GENI_S_CMD_CTRL_REG, + S_GENI_CMD_ABORT, false); + geni_write_reg_nolog(irq_clear, uport->membase, SE_GENI_S_IRQ_CLEAR); + geni_write_reg(FORCE_DEFAULT, uport->membase, GENI_FORCE_DEFAULT_REG); +} + +static void msm_geni_serial_complete_rx_eot(struct uart_port *uport) +{ + int poll_done = 0, tries = 0; + struct msm_geni_serial_port *port = GET_DEV_PORT(uport); + + do { + poll_done = msm_geni_serial_poll_bit(uport, SE_DMA_RX_IRQ_STAT, + RX_EOT, true); + tries++; + } while (!poll_done && tries < 5); + + if (!poll_done) + IPC_LOG_MSG(port->ipc_log_misc, + "%s: RX_EOT, GENI:0x%x, DMA_DEBUG:0x%x\n", __func__, + geni_read_reg_nolog(uport->membase, SE_GENI_STATUS), + geni_read_reg_nolog(uport->membase, SE_DMA_DEBUG_REG0)); + else + geni_write_reg_nolog(RX_EOT, uport->membase, SE_DMA_RX_IRQ_CLR); +} + +#ifdef CONFIG_CONSOLE_POLL +static int msm_geni_serial_get_char(struct uart_port *uport) +{ + unsigned int rx_fifo; + unsigned int m_irq_status; + unsigned int s_irq_status; + + if (!(msm_geni_serial_poll_bit(uport, SE_GENI_M_IRQ_STATUS, + M_SEC_IRQ_EN, true))) + return -ENXIO; + + m_irq_status = geni_read_reg_nolog(uport->membase, + SE_GENI_M_IRQ_STATUS); + s_irq_status = geni_read_reg_nolog(uport->membase, + SE_GENI_S_IRQ_STATUS); + geni_write_reg_nolog(m_irq_status, uport->membase, + SE_GENI_M_IRQ_CLEAR); + geni_write_reg_nolog(s_irq_status, uport->membase, + SE_GENI_S_IRQ_CLEAR); + + if (!(msm_geni_serial_poll_bit(uport, SE_GENI_RX_FIFO_STATUS, + RX_FIFO_WC_MSK, true))) + return -ENXIO; + + /* + * Read the Rx FIFO only after clearing the interrupt registers and + * getting valid RX fifo status. + */ + mb(); + rx_fifo = geni_read_reg_nolog(uport->membase, SE_GENI_RX_FIFOn); + rx_fifo &= 0xFF; + return rx_fifo; +} + +static void msm_geni_serial_poll_put_char(struct uart_port *uport, + unsigned char c) +{ + int b = (int) c; + struct msm_geni_serial_port *port = GET_DEV_PORT(uport); + + geni_write_reg_nolog(port->tx_wm, uport->membase, + SE_GENI_TX_WATERMARK_REG); + msm_geni_serial_setup_tx(uport, 1); + if (!msm_geni_serial_poll_bit(uport, SE_GENI_M_IRQ_STATUS, + M_TX_FIFO_WATERMARK_EN, true)) + WARN_ON(1); + geni_write_reg_nolog(b, uport->membase, SE_GENI_TX_FIFOn); + geni_write_reg_nolog(M_TX_FIFO_WATERMARK_EN, uport->membase, + SE_GENI_M_IRQ_CLEAR); + /* + * Ensure FIFO write goes through before polling for status but. + */ + mb(); + msm_geni_serial_poll_cancel_tx(uport); +} +#endif + +#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(CONFIG_CONSOLE_POLL) +static void msm_geni_serial_wr_char(struct uart_port *uport, int ch) +{ + geni_write_reg_nolog(ch, uport->membase, SE_GENI_TX_FIFOn); + /* + * Ensure FIFO write clear goes through before + * next iteration. + */ + mb(); + +} + +static void +__msm_geni_serial_console_write(struct uart_port *uport, const char *s, + unsigned int count) +{ + int new_line = 0; + int i; + int bytes_to_send = count; + int fifo_depth = DEF_FIFO_DEPTH_WORDS; + int tx_wm = DEF_TX_WM; + + for (i = 0; i < count; i++) { + if (s[i] == '\n') + new_line++; + } + + bytes_to_send += new_line; + geni_write_reg_nolog(tx_wm, uport->membase, + SE_GENI_TX_WATERMARK_REG); + msm_geni_serial_setup_tx(uport, bytes_to_send); + i = 0; + while (i < count) { + u32 chars_to_write = 0; + u32 avail_fifo_bytes = (fifo_depth - tx_wm); + /* + * If the WM bit never set, then the Tx state machine is not + * in a valid state, so break, cancel/abort any existing + * command. Unfortunately the current data being written is + * lost. + */ + while (!msm_geni_serial_poll_bit(uport, SE_GENI_M_IRQ_STATUS, + M_TX_FIFO_WATERMARK_EN, true)) + break; + chars_to_write = min((unsigned int)(count - i), + avail_fifo_bytes); + if ((chars_to_write << 1) > avail_fifo_bytes) + chars_to_write = (avail_fifo_bytes >> 1); + uart_console_write(uport, (s + i), chars_to_write, + msm_geni_serial_wr_char); + geni_write_reg_nolog(M_TX_FIFO_WATERMARK_EN, uport->membase, + SE_GENI_M_IRQ_CLEAR); + /* Ensure this goes through before polling for WM IRQ again.*/ + mb(); + i += chars_to_write; + } + msm_geni_serial_poll_cancel_tx(uport); +} + +static void msm_geni_serial_console_write(struct console *co, const char *s, + unsigned int count) +{ + struct uart_port *uport; + struct msm_geni_serial_port *port; + int locked = 1; + unsigned long flags; + + WARN_ON(co->index < 0 || co->index >= GENI_UART_NR_PORTS); + + port = get_port_from_line(co->index, true); + if (IS_ERR_OR_NULL(port)) + return; + + uport = &port->uport; + if (oops_in_progress) + locked = spin_trylock_irqsave(&uport->lock, flags); + else + spin_lock_irqsave(&uport->lock, flags); + + if (locked) { + __msm_geni_serial_console_write(uport, s, count); + spin_unlock_irqrestore(&uport->lock, flags); + } +} + +static int handle_rx_console(struct uart_port *uport, + unsigned int rx_fifo_wc, + unsigned int rx_last_byte_valid, + unsigned int rx_last, + bool drop_rx) +{ + int i, c; + unsigned char *rx_char; + struct tty_port *tport; + struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport); + + tport = &uport->state->port; + for (i = 0; i < rx_fifo_wc; i++) { + int bytes = 4; + + *(msm_port->rx_fifo) = + geni_read_reg_nolog(uport->membase, SE_GENI_RX_FIFOn); + if (drop_rx) + continue; + rx_char = (unsigned char *)msm_port->rx_fifo; + + if (i == (rx_fifo_wc - 1)) { + if (rx_last && rx_last_byte_valid) + bytes = rx_last_byte_valid; + } + for (c = 0; c < bytes; c++) { + char flag = TTY_NORMAL; + int sysrq; + + uport->icount.rx++; + sysrq = uart_handle_sysrq_char(uport, rx_char[c]); + if (!sysrq) + tty_insert_flip_char(tport, rx_char[c], flag); + } + } + if (!drop_rx) + tty_flip_buffer_push(tport); + return 0; +} +#else +static int handle_rx_console(struct uart_port *uport, + unsigned int rx_fifo_wc, + unsigned int rx_last_byte_valid, + unsigned int rx_last, + bool drop_rx) +{ + return -EPERM; +} + +#endif /* (CONFIG_SERIAL_CORE_CONSOLE) || defined(CONFIG_CONSOLE_POLL)) */ + +static int msm_geni_serial_prep_dma_tx(struct uart_port *uport) +{ + struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport); + struct circ_buf *xmit = &uport->state->xmit; + unsigned int xmit_size; + int ret = 0; + + xmit_size = uart_circ_chars_pending(xmit); + if (xmit_size < WAKEUP_CHARS) + uart_write_wakeup(uport); + + if (xmit_size > (UART_XMIT_SIZE - xmit->tail)) + xmit_size = UART_XMIT_SIZE - xmit->tail; + + if (!xmit_size) + return ret; + + dump_ipc(msm_port->ipc_log_tx, "DMA Tx", + (char *)&xmit->buf[xmit->tail], 0, xmit_size); + msm_geni_serial_setup_tx(uport, xmit_size); + ret = geni_se_tx_dma_prep(msm_port->wrapper_dev, uport->membase, + &xmit->buf[xmit->tail], xmit_size, &msm_port->tx_dma); + if (!ret) { + msm_port->xmit_size = xmit_size; + } else { + geni_write_reg_nolog(0, uport->membase, + SE_UART_TX_TRANS_LEN); + geni_cancel_m_cmd(uport->membase); + if (!msm_geni_serial_poll_bit(uport, SE_GENI_M_IRQ_STATUS, + M_CMD_CANCEL_EN, true)) { + geni_abort_m_cmd(uport->membase); + msm_geni_serial_poll_bit(uport, SE_GENI_M_IRQ_STATUS, + M_CMD_ABORT_EN, true); + geni_write_reg_nolog(M_CMD_ABORT_EN, uport->membase, + SE_GENI_M_IRQ_CLEAR); + } + geni_write_reg_nolog(M_CMD_CANCEL_EN, uport->membase, + SE_GENI_M_IRQ_CLEAR); + IPC_LOG_MSG(msm_port->ipc_log_tx, "%s: DMA map failure %d\n", + __func__, ret); + msm_port->tx_dma = (dma_addr_t)NULL; + msm_port->xmit_size = 0; + } + return ret; +} + +static void msm_geni_serial_start_tx(struct uart_port *uport) +{ + unsigned int geni_m_irq_en; + struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport); + unsigned int geni_status; + unsigned int geni_ios; + + if (!uart_console(uport) && !pm_runtime_active(uport->dev)) { + IPC_LOG_MSG(msm_port->ipc_log_misc, + "%s.Putting in async RPM vote\n", __func__); + pm_runtime_get(uport->dev); + goto exit_start_tx; + } + + if (!uart_console(uport)) { + IPC_LOG_MSG(msm_port->ipc_log_misc, + "%s.Power on.\n", __func__); + pm_runtime_get(uport->dev); + } + + if (msm_port->xfer_mode == FIFO_MODE) { + geni_status = geni_read_reg_nolog(uport->membase, + SE_GENI_STATUS); + if (geni_status & M_GENI_CMD_ACTIVE) + goto check_flow_ctrl; + + if (!msm_geni_serial_tx_empty(uport)) + goto check_flow_ctrl; + + geni_m_irq_en = geni_read_reg_nolog(uport->membase, + SE_GENI_M_IRQ_EN); + geni_m_irq_en |= (M_TX_FIFO_WATERMARK_EN | M_CMD_DONE_EN); + + geni_write_reg_nolog(msm_port->tx_wm, uport->membase, + SE_GENI_TX_WATERMARK_REG); + geni_write_reg_nolog(geni_m_irq_en, uport->membase, + SE_GENI_M_IRQ_EN); + /* Geni command setup should complete before returning.*/ + mb(); + } else if (msm_port->xfer_mode == SE_DMA) { + if (msm_port->tx_dma) + goto check_flow_ctrl; + + msm_geni_serial_prep_dma_tx(uport); + } + return; +check_flow_ctrl: + geni_ios = geni_read_reg_nolog(uport->membase, SE_GENI_IOS); + if (!(geni_ios & IO2_DATA_IN)) + IPC_LOG_MSG(msm_port->ipc_log_misc, "%s: ios: 0x%08x\n", + __func__, geni_ios); +exit_start_tx: + if (!uart_console(uport)) + msm_geni_serial_power_off(uport); +} + +static void msm_geni_serial_tx_fsm_rst(struct uart_port *uport) +{ + unsigned int tx_irq_en; + int done = 0; + int tries = 0; + + tx_irq_en = geni_read_reg_nolog(uport->membase, SE_DMA_TX_IRQ_EN); + geni_write_reg_nolog(0, uport->membase, SE_DMA_TX_IRQ_EN_SET); + geni_write_reg_nolog(1, uport->membase, SE_DMA_TX_FSM_RST); + do { + done = msm_geni_serial_poll_bit(uport, SE_DMA_TX_IRQ_STAT, + TX_RESET_DONE, true); + tries++; + } while (!done && tries < 5); + geni_write_reg_nolog(TX_DMA_DONE | TX_RESET_DONE, uport->membase, + SE_DMA_TX_IRQ_CLR); + geni_write_reg_nolog(tx_irq_en, uport->membase, SE_DMA_TX_IRQ_EN_SET); +} + +static void stop_tx_sequencer(struct uart_port *uport) +{ + unsigned int geni_m_irq_en; + unsigned int geni_status; + struct msm_geni_serial_port *port = GET_DEV_PORT(uport); + + geni_m_irq_en = geni_read_reg_nolog(uport->membase, SE_GENI_M_IRQ_EN); + geni_m_irq_en &= ~M_CMD_DONE_EN; + if (port->xfer_mode == FIFO_MODE) { + geni_m_irq_en &= ~M_TX_FIFO_WATERMARK_EN; + geni_write_reg_nolog(0, uport->membase, + SE_GENI_TX_WATERMARK_REG); + } else if (port->xfer_mode == SE_DMA) { + if (port->tx_dma) { + msm_geni_serial_tx_fsm_rst(uport); + geni_se_tx_dma_unprep(port->wrapper_dev, port->tx_dma, + port->xmit_size); + port->tx_dma = (dma_addr_t)NULL; + } + } + port->xmit_size = 0; + geni_write_reg_nolog(geni_m_irq_en, uport->membase, SE_GENI_M_IRQ_EN); + geni_status = geni_read_reg_nolog(uport->membase, + SE_GENI_STATUS); + /* Possible stop tx is called multiple times. */ + if (!(geni_status & M_GENI_CMD_ACTIVE)) + return; + + geni_cancel_m_cmd(uport->membase); + if (!msm_geni_serial_poll_bit(uport, SE_GENI_M_IRQ_STATUS, + M_CMD_CANCEL_EN, true)) { + geni_abort_m_cmd(uport->membase); + msm_geni_serial_poll_bit(uport, SE_GENI_M_IRQ_STATUS, + M_CMD_ABORT_EN, true); + geni_write_reg_nolog(M_CMD_ABORT_EN, uport->membase, + SE_GENI_M_IRQ_CLEAR); + } + geni_write_reg_nolog(M_CMD_CANCEL_EN, uport->membase, + SE_GENI_M_IRQ_CLEAR); + /* + * If we end up having to cancel an on-going Tx for non-console usecase + * then it means there was some unsent data in the Tx FIFO, consequently + * it means that there is a vote imbalance as we put in a vote during + * start_tx() that is removed only as part of a "done" ISR. To balance + * this out, remove the vote put in during start_tx(). + */ + if (!uart_console(uport)) { + IPC_LOG_MSG(port->ipc_log_misc, "%s:Removing vote\n", __func__); + msm_geni_serial_power_off(uport); + } + IPC_LOG_MSG(port->ipc_log_misc, "%s:\n", __func__); +} + +static void msm_geni_serial_stop_tx(struct uart_port *uport) +{ + struct msm_geni_serial_port *port = GET_DEV_PORT(uport); + + if (!uart_console(uport) && device_pending_suspend(uport)) { + dev_err(uport->dev, "%s.Device is suspended.\n", __func__); + IPC_LOG_MSG(port->ipc_log_misc, + "%s.Device is suspended.\n", __func__); + return; + } + stop_tx_sequencer(uport); +} + +static void start_rx_sequencer(struct uart_port *uport) +{ + unsigned int geni_s_irq_en; + unsigned int geni_m_irq_en; + unsigned int geni_status; + struct msm_geni_serial_port *port = GET_DEV_PORT(uport); + int ret; + u32 geni_se_param = UART_PARAM_RFR_OPEN; + + geni_status = geni_read_reg_nolog(uport->membase, SE_GENI_STATUS); + if (geni_status & S_GENI_CMD_ACTIVE) + msm_geni_serial_stop_rx(uport); + + /* Start RX with the RFR_OPEN to keep RFR in always ready state */ + geni_setup_s_cmd(uport->membase, UART_START_READ, geni_se_param); + + if (port->xfer_mode == FIFO_MODE) { + geni_s_irq_en = geni_read_reg_nolog(uport->membase, + SE_GENI_S_IRQ_EN); + geni_m_irq_en = geni_read_reg_nolog(uport->membase, + SE_GENI_M_IRQ_EN); + + geni_s_irq_en |= S_RX_FIFO_WATERMARK_EN | S_RX_FIFO_LAST_EN; + geni_m_irq_en |= M_RX_FIFO_WATERMARK_EN | M_RX_FIFO_LAST_EN; + + geni_write_reg_nolog(geni_s_irq_en, uport->membase, + SE_GENI_S_IRQ_EN); + geni_write_reg_nolog(geni_m_irq_en, uport->membase, + SE_GENI_M_IRQ_EN); + } else if (port->xfer_mode == SE_DMA) { + ret = geni_se_rx_dma_prep(port->wrapper_dev, uport->membase, + port->rx_buf, DMA_RX_BUF_SIZE, &port->rx_dma); + if (ret) { + dev_err(uport->dev, "%s: RX Prep dma failed %d\n", + __func__, ret); + msm_geni_serial_stop_rx(uport); + goto exit_start_rx_sequencer; + } + } + /* + * Ensure the writes to the secondary sequencer and interrupt enables + * go through. + */ + mb(); + geni_status = geni_read_reg_nolog(uport->membase, SE_GENI_STATUS); +exit_start_rx_sequencer: + IPC_LOG_MSG(port->ipc_log_misc, "%s 0x%x\n", __func__, geni_status); +} + +static void msm_geni_serial_start_rx(struct uart_port *uport) +{ + struct msm_geni_serial_port *port = GET_DEV_PORT(uport); + + if (!uart_console(uport) && device_pending_suspend(uport)) { + dev_err(uport->dev, "%s.Device is suspended.\n", __func__); + IPC_LOG_MSG(port->ipc_log_misc, + "%s.Device is suspended.\n", __func__); + return; + } + start_rx_sequencer(&port->uport); +} + + +static void msm_geni_serial_rx_fsm_rst(struct uart_port *uport) +{ + unsigned int rx_irq_en; + int done = 0; + int tries = 0; + + rx_irq_en = geni_read_reg_nolog(uport->membase, SE_DMA_RX_IRQ_EN); + geni_write_reg_nolog(0, uport->membase, SE_DMA_RX_IRQ_EN_SET); + geni_write_reg_nolog(1, uport->membase, SE_DMA_RX_FSM_RST); + do { + done = msm_geni_serial_poll_bit(uport, SE_DMA_RX_IRQ_STAT, + RX_RESET_DONE, true); + tries++; + } while (!done && tries < 5); + geni_write_reg_nolog(RX_DMA_DONE | RX_RESET_DONE, uport->membase, + SE_DMA_RX_IRQ_CLR); + geni_write_reg_nolog(rx_irq_en, uport->membase, SE_DMA_RX_IRQ_EN_SET); +} + +static void stop_rx_sequencer(struct uart_port *uport) +{ + unsigned int geni_s_irq_en; + unsigned int geni_m_irq_en; + unsigned int geni_status; + struct msm_geni_serial_port *port = GET_DEV_PORT(uport); + u32 irq_clear = S_CMD_CANCEL_EN; + bool done; + + IPC_LOG_MSG(port->ipc_log_misc, "%s\n", __func__); + if (port->xfer_mode == FIFO_MODE) { + geni_s_irq_en = geni_read_reg_nolog(uport->membase, + SE_GENI_S_IRQ_EN); + geni_m_irq_en = geni_read_reg_nolog(uport->membase, + SE_GENI_M_IRQ_EN); + geni_s_irq_en &= ~(S_RX_FIFO_WATERMARK_EN | S_RX_FIFO_LAST_EN); + geni_m_irq_en &= ~(M_RX_FIFO_WATERMARK_EN | M_RX_FIFO_LAST_EN); + + geni_write_reg_nolog(geni_s_irq_en, uport->membase, + SE_GENI_S_IRQ_EN); + geni_write_reg_nolog(geni_m_irq_en, uport->membase, + SE_GENI_M_IRQ_EN); + } + + geni_status = geni_read_reg_nolog(uport->membase, SE_GENI_STATUS); + /* Possible stop rx is called multiple times. */ + if (!(geni_status & S_GENI_CMD_ACTIVE)) + goto exit_rx_seq; + + geni_cancel_s_cmd(uport->membase); + /* + * Ensure that the cancel goes through before polling for the + * cancel control bit. + */ + mb(); + if (!uart_console(uport)) + msm_geni_serial_complete_rx_eot(uport); + + done = msm_geni_serial_poll_bit(uport, SE_GENI_S_CMD_CTRL_REG, + S_GENI_CMD_CANCEL, false); + if (done) { + geni_write_reg_nolog(irq_clear, uport->membase, + SE_GENI_S_IRQ_CLEAR); + goto exit_rx_seq; + } else { + IPC_LOG_MSG(port->ipc_log_misc, "%s Cancel fail 0x%x\n", + __func__, geni_status); + } + + geni_status = geni_read_reg_nolog(uport->membase, SE_GENI_STATUS); + if ((geni_status & S_GENI_CMD_ACTIVE)) { + IPC_LOG_MSG(port->ipc_log_misc, "%s:Abort Rx, GENI:0x%x\n", + __func__, geni_status); + msm_geni_serial_abort_rx(uport); + } +exit_rx_seq: + if (port->xfer_mode == SE_DMA && port->rx_dma) { + msm_geni_serial_rx_fsm_rst(uport); + geni_se_rx_dma_unprep(port->wrapper_dev, port->rx_dma, + DMA_RX_BUF_SIZE); + port->rx_dma = (dma_addr_t)NULL; + } +} + +static void msm_geni_serial_stop_rx(struct uart_port *uport) +{ + struct msm_geni_serial_port *port = GET_DEV_PORT(uport); + + if (!uart_console(uport) && device_pending_suspend(uport)) { + IPC_LOG_MSG(port->ipc_log_misc, + "%s.Device is suspended.\n", __func__); + return; + } + stop_rx_sequencer(uport); +} + +static int handle_rx_hs(struct uart_port *uport, + unsigned int rx_fifo_wc, + unsigned int rx_last_byte_valid, + unsigned int rx_last, + bool drop_rx) +{ + unsigned char *rx_char; + struct tty_port *tport; + struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport); + int ret; + int rx_bytes = 0; + + rx_bytes = (msm_port->tx_fifo_width * (rx_fifo_wc - 1)) >> 3; + rx_bytes += ((rx_last && rx_last_byte_valid) ? + rx_last_byte_valid : msm_port->tx_fifo_width >> 3); + + tport = &uport->state->port; + ioread32_rep((uport->membase + SE_GENI_RX_FIFOn), msm_port->rx_fifo, + rx_fifo_wc); + if (drop_rx) + return 0; + + rx_char = (unsigned char *)msm_port->rx_fifo; + ret = tty_insert_flip_string(tport, rx_char, rx_bytes); + if (ret != rx_bytes) { + dev_err(uport->dev, "%s: ret %d rx_bytes %d\n", __func__, + ret, rx_bytes); + WARN_ON(1); + } + uport->icount.rx += ret; + tty_flip_buffer_push(tport); + dump_ipc(msm_port->ipc_log_rx, "Rx", (char *)msm_port->rx_fifo, 0, + rx_bytes); + return ret; +} + +static int msm_geni_serial_handle_rx(struct uart_port *uport, bool drop_rx) +{ + int ret = 0; + unsigned int rx_fifo_status; + unsigned int rx_fifo_wc = 0; + unsigned int rx_last_byte_valid = 0; + unsigned int rx_last = 0; + struct tty_port *tport; + struct msm_geni_serial_port *port = GET_DEV_PORT(uport); + + tport = &uport->state->port; + rx_fifo_status = geni_read_reg_nolog(uport->membase, + SE_GENI_RX_FIFO_STATUS); + rx_fifo_wc = rx_fifo_status & RX_FIFO_WC_MSK; + rx_last_byte_valid = ((rx_fifo_status & RX_LAST_BYTE_VALID_MSK) >> + RX_LAST_BYTE_VALID_SHFT); + rx_last = rx_fifo_status & RX_LAST; + if (rx_fifo_wc) + ret = port->handle_rx(uport, rx_fifo_wc, rx_last_byte_valid, + rx_last, drop_rx); + return ret; +} + +static int msm_geni_serial_handle_tx(struct uart_port *uport) +{ + struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport); + struct circ_buf *xmit = &uport->state->xmit; + unsigned int avail_fifo_bytes = 0; + unsigned int bytes_remaining = 0; + int i = 0; + unsigned int tx_fifo_status; + unsigned int xmit_size; + unsigned int fifo_width_bytes = + (uart_console(uport) ? 1 : (msm_port->tx_fifo_width >> 3)); + int temp_tail = 0; + + xmit_size = uart_circ_chars_pending(xmit); + tx_fifo_status = geni_read_reg_nolog(uport->membase, + SE_GENI_TX_FIFO_STATUS); + /* Both FIFO and framework buffer are drained */ + if (!xmit_size && !tx_fifo_status) { + msm_geni_serial_stop_tx(uport); + goto exit_handle_tx; + } + + avail_fifo_bytes = (msm_port->tx_fifo_depth - msm_port->tx_wm) * + fifo_width_bytes; + temp_tail = xmit->tail & (UART_XMIT_SIZE - 1); + + if (xmit_size > (UART_XMIT_SIZE - temp_tail)) + xmit_size = (UART_XMIT_SIZE - temp_tail); + if (xmit_size > avail_fifo_bytes) + xmit_size = avail_fifo_bytes; + if (!xmit_size) + goto exit_handle_tx; + + msm_geni_serial_setup_tx(uport, xmit_size); + bytes_remaining = xmit_size; + while (i < xmit_size) { + unsigned int tx_bytes; + unsigned int buf = 0; + int c; + + tx_bytes = ((bytes_remaining < fifo_width_bytes) ? + bytes_remaining : fifo_width_bytes); + + for (c = 0; c < tx_bytes ; c++) + buf |= (xmit->buf[temp_tail + c] << (c * 8)); + geni_write_reg_nolog(buf, uport->membase, SE_GENI_TX_FIFOn); + i += tx_bytes; + bytes_remaining -= tx_bytes; + uport->icount.tx += tx_bytes; + temp_tail += tx_bytes; + /* Ensure FIFO write goes through */ + wmb(); + } + xmit->tail = temp_tail & (UART_XMIT_SIZE - 1); + if (uart_console(uport)) + msm_geni_serial_poll_cancel_tx(uport); +exit_handle_tx: + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(uport); + return 0; +} + +static int msm_geni_serial_handle_dma_rx(struct uart_port *uport, bool drop_rx) +{ + struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport); + unsigned int rx_bytes = 0; + struct tty_port *tport; + int ret; + unsigned int geni_status; + + geni_status = geni_read_reg_nolog(uport->membase, SE_GENI_STATUS); + /* Possible stop rx is called */ + if (!(geni_status & S_GENI_CMD_ACTIVE)) + return 0; + + geni_se_rx_dma_unprep(msm_port->wrapper_dev, msm_port->rx_dma, + DMA_RX_BUF_SIZE); + msm_port->rx_dma = (dma_addr_t)NULL; + + rx_bytes = geni_read_reg_nolog(uport->membase, SE_DMA_RX_LEN_IN); + if (unlikely(!msm_port->rx_buf)) { + IPC_LOG_MSG(msm_port->ipc_log_rx, "%s: NULL Rx_buf\n", + __func__); + return 0; + } + if (unlikely(!rx_bytes)) { + IPC_LOG_MSG(msm_port->ipc_log_rx, "%s: Size %d\n", + __func__, rx_bytes); + goto exit_handle_dma_rx; + } + if (drop_rx) + goto exit_handle_dma_rx; + + tport = &uport->state->port; + ret = tty_insert_flip_string(tport, (unsigned char *)(msm_port->rx_buf), + rx_bytes); + if (ret != rx_bytes) { + dev_err(uport->dev, "%s: ret %d rx_bytes %d\n", __func__, + ret, rx_bytes); + WARN_ON(1); + } + uport->icount.rx += ret; + tty_flip_buffer_push(tport); + dump_ipc(msm_port->ipc_log_rx, "DMA Rx", (char *)msm_port->rx_buf, 0, + rx_bytes); +exit_handle_dma_rx: + ret = geni_se_rx_dma_prep(msm_port->wrapper_dev, uport->membase, + msm_port->rx_buf, DMA_RX_BUF_SIZE, &msm_port->rx_dma); + if (ret) + IPC_LOG_MSG(msm_port->ipc_log_rx, "%s: %d\n", __func__, ret); + return ret; +} + +static int msm_geni_serial_handle_dma_tx(struct uart_port *uport) +{ + struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport); + struct circ_buf *xmit = &uport->state->xmit; + + xmit->tail = (xmit->tail + msm_port->xmit_size) & (UART_XMIT_SIZE - 1); + geni_se_tx_dma_unprep(msm_port->wrapper_dev, msm_port->tx_dma, + msm_port->xmit_size); + uport->icount.tx += msm_port->xmit_size; + msm_port->tx_dma = (dma_addr_t)NULL; + msm_port->xmit_size = 0; + + if (!uart_circ_empty(xmit)) + msm_geni_serial_prep_dma_tx(uport); + else { + /* + * This will balance out the power vote put in during start_tx + * allowing the device to suspend. + */ + if (!uart_console(uport)) { + IPC_LOG_MSG(msm_port->ipc_log_misc, + "%s.Power Off.\n", __func__); + msm_geni_serial_power_off(uport); + } + uart_write_wakeup(uport); + } + return 0; +} + +static irqreturn_t msm_geni_serial_isr(int isr, void *dev) +{ + unsigned int m_irq_status; + unsigned int s_irq_status; + unsigned int dma; + unsigned int dma_tx_status; + unsigned int dma_rx_status; + struct uart_port *uport = dev; + unsigned long flags; + unsigned int m_irq_en; + struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport); + struct tty_port *tport = &uport->state->port; + bool drop_rx = false; + + spin_lock_irqsave(&uport->lock, flags); + if (uart_console(uport) && uport->suspended) + goto exit_geni_serial_isr; + if (!uart_console(uport) && pm_runtime_status_suspended(uport->dev)) { + dev_err(uport->dev, "%s.Device is suspended.\n", __func__); + IPC_LOG_MSG(msm_port->ipc_log_misc, + "%s.Device is suspended.\n", __func__); + goto exit_geni_serial_isr; + } + m_irq_status = geni_read_reg_nolog(uport->membase, + SE_GENI_M_IRQ_STATUS); + s_irq_status = geni_read_reg_nolog(uport->membase, + SE_GENI_S_IRQ_STATUS); + m_irq_en = geni_read_reg_nolog(uport->membase, SE_GENI_M_IRQ_EN); + dma = geni_read_reg_nolog(uport->membase, SE_GENI_DMA_MODE_EN); + dma_tx_status = geni_read_reg_nolog(uport->membase, SE_DMA_TX_IRQ_STAT); + dma_rx_status = geni_read_reg_nolog(uport->membase, SE_DMA_RX_IRQ_STAT); + + geni_write_reg_nolog(m_irq_status, uport->membase, SE_GENI_M_IRQ_CLEAR); + geni_write_reg_nolog(s_irq_status, uport->membase, SE_GENI_S_IRQ_CLEAR); + + if ((m_irq_status & M_ILLEGAL_CMD_EN)) { + WARN_ON(1); + goto exit_geni_serial_isr; + } + + if (s_irq_status & S_RX_FIFO_WR_ERR_EN) { + uport->icount.overrun++; + tty_insert_flip_char(tport, 0, TTY_OVERRUN); + IPC_LOG_MSG(msm_port->ipc_log_misc, + "%s.sirq 0x%x buf_overrun:%d\n", + __func__, s_irq_status, uport->icount.buf_overrun); + } + + if (!dma) { + if ((m_irq_status & m_irq_en) & + (M_TX_FIFO_WATERMARK_EN | M_CMD_DONE_EN)) + msm_geni_serial_handle_tx(uport); + + if ((s_irq_status & S_GP_IRQ_0_EN) || + (s_irq_status & S_GP_IRQ_1_EN)) { + if (s_irq_status & S_GP_IRQ_0_EN) + uport->icount.parity++; + IPC_LOG_MSG(msm_port->ipc_log_misc, + "%s.sirq 0x%x parity:%d\n", + __func__, s_irq_status, uport->icount.parity); + drop_rx = true; + } else if ((s_irq_status & S_GP_IRQ_2_EN) || + (s_irq_status & S_GP_IRQ_3_EN)) { + uport->icount.brk++; + IPC_LOG_MSG(msm_port->ipc_log_misc, + "%s.sirq 0x%x break:%d\n", + __func__, s_irq_status, uport->icount.brk); + } + + if ((s_irq_status & S_RX_FIFO_WATERMARK_EN) || + (s_irq_status & S_RX_FIFO_LAST_EN)) + msm_geni_serial_handle_rx(uport, drop_rx); + } else { + if (dma_tx_status) { + geni_write_reg_nolog(dma_tx_status, uport->membase, + SE_DMA_TX_IRQ_CLR); + if (dma_tx_status & TX_DMA_DONE) + msm_geni_serial_handle_dma_tx(uport); + } + + if (dma_rx_status) { + geni_write_reg_nolog(dma_rx_status, uport->membase, + SE_DMA_RX_IRQ_CLR); + if (dma_rx_status & RX_RESET_DONE) { + IPC_LOG_MSG(msm_port->ipc_log_misc, + "%s.Reset done. 0x%x.\n", + __func__, dma_rx_status); + goto exit_geni_serial_isr; + } + if (dma_rx_status & UART_DMA_RX_ERRS) { + if (dma_rx_status & UART_DMA_RX_PARITY_ERR) + uport->icount.parity++; + IPC_LOG_MSG(msm_port->ipc_log_misc, + "%s.Rx Errors. 0x%x parity:%d\n", + __func__, dma_rx_status, + uport->icount.parity); + drop_rx = true; + } else if (dma_rx_status & UART_DMA_RX_BREAK) { + uport->icount.brk++; + IPC_LOG_MSG(msm_port->ipc_log_misc, + "%s.Rx Errors. 0x%x break:%d\n", + __func__, dma_rx_status, + uport->icount.brk); + } + if (dma_rx_status & RX_DMA_DONE) + msm_geni_serial_handle_dma_rx(uport, drop_rx); + } + } + +exit_geni_serial_isr: + spin_unlock_irqrestore(&uport->lock, flags); + return IRQ_HANDLED; +} + +static irqreturn_t msm_geni_wakeup_isr(int isr, void *dev) +{ + struct uart_port *uport = dev; + struct msm_geni_serial_port *port = GET_DEV_PORT(uport); + struct tty_struct *tty; + unsigned long flags; + + spin_lock_irqsave(&uport->lock, flags); + IPC_LOG_MSG(port->ipc_log_rx, "%s: Edge-Count %d\n", __func__, + port->edge_count); + if (port->wakeup_byte && (port->edge_count == 2)) { + tty = uport->state->port.tty; + tty_insert_flip_char(tty->port, port->wakeup_byte, TTY_NORMAL); + IPC_LOG_MSG(port->ipc_log_rx, "%s: Inject 0x%x\n", + __func__, port->wakeup_byte); + port->edge_count = 0; + tty_flip_buffer_push(tty->port); + __pm_wakeup_event(&port->geni_wake, WAKEBYTE_TIMEOUT_MSEC); + } else if (port->edge_count < 2) { + port->edge_count++; + } + spin_unlock_irqrestore(&uport->lock, flags); + return IRQ_HANDLED; +} + +static int get_tx_fifo_size(struct msm_geni_serial_port *port) +{ + struct uart_port *uport; + + if (!port) + return -ENODEV; + + uport = &port->uport; + port->tx_fifo_depth = get_tx_fifo_depth(uport->membase); + if (!port->tx_fifo_depth) { + dev_err(uport->dev, "%s:Invalid TX FIFO depth read\n", + __func__); + return -ENXIO; + } + + port->tx_fifo_width = get_tx_fifo_width(uport->membase); + if (!port->tx_fifo_width) { + dev_err(uport->dev, "%s:Invalid TX FIFO width read\n", + __func__); + return -ENXIO; + } + + port->rx_fifo_depth = get_rx_fifo_depth(uport->membase); + if (!port->rx_fifo_depth) { + dev_err(uport->dev, "%s:Invalid RX FIFO depth read\n", + __func__); + return -ENXIO; + } + + uport->fifosize = + ((port->tx_fifo_depth * port->tx_fifo_width) >> 3); + return 0; +} + +static void set_rfr_wm(struct msm_geni_serial_port *port) +{ + /* + * Set RFR (Flow off) to FIFO_DEPTH - 2. + * RX WM level at 50% RX_FIFO_DEPTH. + * TX WM level at 10% TX_FIFO_DEPTH. + */ + port->rx_rfr = port->rx_fifo_depth - 2; + if (!uart_console(&port->uport)) + port->rx_wm = port->rx_fifo_depth >> 1; + else + port->rx_wm = UART_CONSOLE_RX_WM; + port->tx_wm = 2; +} + +static void msm_geni_serial_shutdown(struct uart_port *uport) +{ + struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport); + unsigned long flags; + + /* Stop the console before stopping the current tx */ + if (uart_console(uport)) { + console_stop(uport->cons); + } else { + msm_geni_serial_power_on(uport); + wait_for_transfers_inflight(uport); + } + + disable_irq(uport->irq); + free_irq(uport->irq, uport); + spin_lock_irqsave(&uport->lock, flags); + msm_geni_serial_stop_tx(uport); + msm_geni_serial_stop_rx(uport); + spin_unlock_irqrestore(&uport->lock, flags); + + if (!uart_console(uport)) { + if (msm_port->ioctl_count) { + int i; + + for (i = 0; i < msm_port->ioctl_count; i++) { + IPC_LOG_MSG(msm_port->ipc_log_pwr, + "%s IOCTL vote present. Forcing off\n", + __func__); + msm_geni_serial_power_off(uport); + } + msm_port->ioctl_count = 0; + } + msm_geni_serial_power_off(uport); + if (msm_port->wakeup_irq > 0) { + irq_set_irq_wake(msm_port->wakeup_irq, 0); + disable_irq(msm_port->wakeup_irq); + free_irq(msm_port->wakeup_irq, uport); + } + } + IPC_LOG_MSG(msm_port->ipc_log_misc, "%s\n", __func__); +} + +static int msm_geni_serial_port_setup(struct uart_port *uport) +{ + int ret = 0; + struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport); + unsigned long cfg0, cfg1; + unsigned int rxstale = DEFAULT_BITS_PER_CHAR * STALE_TIMEOUT; + + set_rfr_wm(msm_port); + geni_write_reg_nolog(rxstale, uport->membase, SE_UART_RX_STALE_CNT); + if (!uart_console(uport)) { + /* For now only assume FIFO mode. */ + msm_port->xfer_mode = SE_DMA; + se_get_packing_config(8, 4, false, &cfg0, &cfg1); + geni_write_reg_nolog(cfg0, uport->membase, + SE_GENI_TX_PACKING_CFG0); + geni_write_reg_nolog(cfg1, uport->membase, + SE_GENI_TX_PACKING_CFG1); + geni_write_reg_nolog(cfg0, uport->membase, + SE_GENI_RX_PACKING_CFG0); + geni_write_reg_nolog(cfg1, uport->membase, + SE_GENI_RX_PACKING_CFG1); + msm_port->handle_rx = handle_rx_hs; + msm_port->rx_fifo = devm_kzalloc(uport->dev, + sizeof(msm_port->rx_fifo_depth * sizeof(u32)), + GFP_KERNEL); + if (!msm_port->rx_fifo) { + ret = -ENOMEM; + goto exit_portsetup; + } + + msm_port->rx_buf = devm_kzalloc(uport->dev, DMA_RX_BUF_SIZE, + GFP_KERNEL); + if (!msm_port->rx_buf) { + devm_kfree(uport->dev, msm_port->rx_fifo); + msm_port->rx_fifo = NULL; + ret = -ENOMEM; + goto exit_portsetup; + } + } else { + /* + * Make an unconditional cancel on the main sequencer to reset + * it else we could end up in data loss scenarios. + */ + msm_port->xfer_mode = FIFO_MODE; + msm_geni_serial_poll_cancel_tx(uport); + se_get_packing_config(8, 1, false, &cfg0, &cfg1); + geni_write_reg_nolog(cfg0, uport->membase, + SE_GENI_TX_PACKING_CFG0); + geni_write_reg_nolog(cfg1, uport->membase, + SE_GENI_TX_PACKING_CFG1); + se_get_packing_config(8, 4, false, &cfg0, &cfg1); + geni_write_reg_nolog(cfg0, uport->membase, + SE_GENI_RX_PACKING_CFG0); + geni_write_reg_nolog(cfg1, uport->membase, + SE_GENI_RX_PACKING_CFG1); + } + ret = geni_se_init(uport->membase, msm_port->rx_wm, msm_port->rx_rfr); + if (ret) { + dev_err(uport->dev, "%s: Fail\n", __func__); + goto exit_portsetup; + } + + ret = geni_se_select_mode(uport->membase, msm_port->xfer_mode); + if (ret) + goto exit_portsetup; + + msm_port->port_setup = true; + /* + * Ensure Port setup related IO completes before returning to + * framework. + */ + mb(); +exit_portsetup: + return ret; +} + +static int msm_geni_serial_startup(struct uart_port *uport) +{ + int ret = 0; + struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport); + + scnprintf(msm_port->name, sizeof(msm_port->name), "msm_serial_geni%d", + uport->line); + + if (likely(!uart_console(uport))) { + ret = msm_geni_serial_power_on(&msm_port->uport); + if (ret) { + dev_err(uport->dev, "%s:Failed to power on %d\n", + __func__, ret); + return ret; + } + } + + if (unlikely(get_se_proto(uport->membase) != UART)) { + dev_err(uport->dev, "%s: Invalid FW %d loaded.\n", + __func__, get_se_proto(uport->membase)); + ret = -ENXIO; + goto exit_startup; + } + IPC_LOG_MSG(msm_port->ipc_log_misc, "%s: FW Ver:0x%x%x\n", + __func__, + get_se_m_fw(uport->membase), get_se_s_fw(uport->membase)); + + get_tx_fifo_size(msm_port); + if (!msm_port->port_setup) { + if (msm_geni_serial_port_setup(uport)) + goto exit_startup; + } + + /* + * Ensure that all the port configuration writes complete + * before returning to the framework. + */ + mb(); + ret = request_irq(uport->irq, msm_geni_serial_isr, IRQF_TRIGGER_HIGH, + msm_port->name, uport); + if (unlikely(ret)) { + dev_err(uport->dev, "%s: Failed to get IRQ ret %d\n", + __func__, ret); + goto exit_startup; + } + + if (msm_port->wakeup_irq > 0) { + ret = request_irq(msm_port->wakeup_irq, msm_geni_wakeup_isr, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "hs_uart_wakeup", uport); + if (unlikely(ret)) { + dev_err(uport->dev, "%s:Failed to get WakeIRQ ret%d\n", + __func__, ret); + goto exit_startup; + } + disable_irq(msm_port->wakeup_irq); + ret = irq_set_irq_wake(msm_port->wakeup_irq, 1); + if (unlikely(ret)) { + dev_err(uport->dev, "%s:Failed to set IRQ wake:%d\n", + __func__, ret); + goto exit_startup; + } + } + IPC_LOG_MSG(msm_port->ipc_log_misc, "%s\n", __func__); +exit_startup: + if (likely(!uart_console(uport))) + msm_geni_serial_power_off(&msm_port->uport); + return ret; +} + +static int get_clk_cfg(unsigned long clk_freq, unsigned long *ser_clk) +{ + unsigned long root_freq[] = {7372800, 14745600, 19200000, 29491200, + 32000000, 48000000, 64000000, 80000000, 96000000, 100000000, + 102400000, 112000000, 120000000, 128000000}; + int i; + int match = -1; + + for (i = 0; i < ARRAY_SIZE(root_freq); i++) { + if (clk_freq > root_freq[i]) + continue; + + if (!(root_freq[i] % clk_freq)) { + match = i; + break; + } + } + if (match != -1) + *ser_clk = root_freq[match]; + else + pr_err("clk_freq %ld\n", clk_freq); + return match; +} + +static void geni_serial_write_term_regs(struct uart_port *uport, u32 loopback, + u32 tx_trans_cfg, u32 tx_parity_cfg, u32 rx_trans_cfg, + u32 rx_parity_cfg, u32 bits_per_char, u32 stop_bit_len, + u32 s_clk_cfg) +{ + geni_write_reg_nolog(loopback, uport->membase, SE_UART_LOOPBACK_CFG); + geni_write_reg_nolog(tx_trans_cfg, uport->membase, + SE_UART_TX_TRANS_CFG); + geni_write_reg_nolog(tx_parity_cfg, uport->membase, + SE_UART_TX_PARITY_CFG); + geni_write_reg_nolog(rx_trans_cfg, uport->membase, + SE_UART_RX_TRANS_CFG); + geni_write_reg_nolog(rx_parity_cfg, uport->membase, + SE_UART_RX_PARITY_CFG); + geni_write_reg_nolog(bits_per_char, uport->membase, + SE_UART_TX_WORD_LEN); + geni_write_reg_nolog(bits_per_char, uport->membase, + SE_UART_RX_WORD_LEN); + geni_write_reg_nolog(stop_bit_len, uport->membase, + SE_UART_TX_STOP_BIT_LEN); + geni_write_reg_nolog(s_clk_cfg, uport->membase, GENI_SER_M_CLK_CFG); + geni_write_reg_nolog(s_clk_cfg, uport->membase, GENI_SER_S_CLK_CFG); + + geni_read_reg_nolog(uport->membase, GENI_SER_M_CLK_CFG); +} + +static int get_clk_div_rate(unsigned int baud, unsigned long *desired_clk_rate) +{ + unsigned long ser_clk; + int dfs_index; + int clk_div = 0; + + *desired_clk_rate = baud * UART_OVERSAMPLING; + dfs_index = get_clk_cfg(*desired_clk_rate, &ser_clk); + if (dfs_index < 0) { + pr_err("%s: Can't find matching DFS entry for baud %d\n", + __func__, baud); + clk_div = -EINVAL; + goto exit_get_clk_div_rate; + } + + clk_div = ser_clk / *desired_clk_rate; + *desired_clk_rate = ser_clk; +exit_get_clk_div_rate: + if (clk_div) + clk_div = clk_div*2; + + return clk_div; +} + +static void msm_geni_serial_set_termios(struct uart_port *uport, + struct ktermios *termios, struct ktermios *old) +{ + unsigned int baud; + unsigned int bits_per_char = 0; + unsigned int tx_trans_cfg; + unsigned int tx_parity_cfg; + unsigned int rx_trans_cfg; + unsigned int rx_parity_cfg; + unsigned int stop_bit_len; + int clk_div; + unsigned long ser_clk_cfg = 0; + struct msm_geni_serial_port *port = GET_DEV_PORT(uport); + unsigned long clk_rate; + unsigned long flags; + + geni_write_reg_nolog(0x21, uport->membase, GENI_SER_M_CLK_CFG); + geni_write_reg_nolog(0x21, uport->membase, GENI_SER_S_CLK_CFG); + geni_read_reg_nolog(uport->membase, GENI_SER_M_CLK_CFG); + + if (!uart_console(uport)) { + int ret = msm_geni_serial_power_on(uport); + + if (ret) { + IPC_LOG_MSG(port->ipc_log_misc, + "%s: Failed to vote clock on:%d\n", + __func__, ret); + return; + } + } + /* Take a spinlock else stop_rx causes a race with an ISR due to Cancel + * and FSM_RESET. This also has a potential race with the dma_map/unmap + * operations of ISR. + */ + spin_lock_irqsave(&uport->lock, flags); + msm_geni_serial_stop_rx(uport); + spin_unlock_irqrestore(&uport->lock, flags); + /* baud rate */ + baud = uart_get_baud_rate(uport, termios, old, 300, 4000000); + port->cur_baud = baud; + clk_div = get_clk_div_rate(baud, &clk_rate); + if (clk_div <= 0) + goto exit_set_termios; + + uport->uartclk = clk_rate; + clk_set_rate(port->serial_rsc.se_clk, clk_rate); + ser_clk_cfg |= SER_CLK_EN; + ser_clk_cfg |= (clk_div << CLK_DIV_SHFT); + + /* parity */ + tx_trans_cfg = geni_read_reg_nolog(uport->membase, + SE_UART_TX_TRANS_CFG); + tx_parity_cfg = geni_read_reg_nolog(uport->membase, + SE_UART_TX_PARITY_CFG); + rx_trans_cfg = geni_read_reg_nolog(uport->membase, + SE_UART_RX_TRANS_CFG); + rx_parity_cfg = geni_read_reg_nolog(uport->membase, + SE_UART_RX_PARITY_CFG); + if (termios->c_cflag & PARENB) { + tx_trans_cfg |= UART_TX_PAR_EN; + rx_trans_cfg |= UART_RX_PAR_EN; + tx_parity_cfg |= PAR_CALC_EN; + rx_parity_cfg |= PAR_CALC_EN; + if (termios->c_cflag & PARODD) { + tx_parity_cfg |= PAR_ODD; + rx_parity_cfg |= PAR_ODD; + } else if (termios->c_cflag & CMSPAR) { + tx_parity_cfg |= PAR_SPACE; + rx_parity_cfg |= PAR_SPACE; + } else { + tx_parity_cfg |= PAR_EVEN; + rx_parity_cfg |= PAR_EVEN; + } + } else { + tx_trans_cfg &= ~UART_TX_PAR_EN; + rx_trans_cfg &= ~UART_RX_PAR_EN; + tx_parity_cfg &= ~PAR_CALC_EN; + rx_parity_cfg &= ~PAR_CALC_EN; + } + + /* bits per char */ + switch (termios->c_cflag & CSIZE) { + case CS5: + bits_per_char = 5; + break; + case CS6: + bits_per_char = 6; + break; + case CS7: + bits_per_char = 7; + break; + case CS8: + default: + bits_per_char = 8; + break; + } + + uport->status &= ~(UPSTAT_AUTOCTS); + /* stop bits */ + if (termios->c_cflag & CSTOPB) + stop_bit_len = TX_STOP_BIT_LEN_2; + else + stop_bit_len = TX_STOP_BIT_LEN_1; + + /* flow control, clear the CTS_MASK bit if using flow control. */ + if (termios->c_cflag & CRTSCTS) { + tx_trans_cfg &= ~UART_CTS_MASK; + uport->status |= UPSTAT_AUTOCTS; + } else { + tx_trans_cfg |= UART_CTS_MASK; + /* status bits to ignore */ + } + + if (likely(baud)) + uart_update_timeout(uport, termios->c_cflag, baud); + + geni_serial_write_term_regs(uport, port->loopback, tx_trans_cfg, + tx_parity_cfg, rx_trans_cfg, rx_parity_cfg, bits_per_char, + stop_bit_len, ser_clk_cfg); + + if (termios->c_cflag & CRTSCTS) { + geni_write_reg_nolog(0x0, uport->membase, SE_UART_MANUAL_RFR); + IPC_LOG_MSG(port->ipc_log_misc, "%s: Manual flow off\n", + __func__); + } + + IPC_LOG_MSG(port->ipc_log_misc, "%s: baud %d\n", __func__, baud); + IPC_LOG_MSG(port->ipc_log_misc, "Tx: trans_cfg%d parity %d\n", + tx_trans_cfg, tx_parity_cfg); + IPC_LOG_MSG(port->ipc_log_misc, "Rx: trans_cfg%d parity %d", + rx_trans_cfg, rx_parity_cfg); + IPC_LOG_MSG(port->ipc_log_misc, "BitsChar%d stop bit%d\n", + bits_per_char, stop_bit_len); +exit_set_termios: + msm_geni_serial_start_rx(uport); + if (!uart_console(uport)) + msm_geni_serial_power_off(uport); + return; + +} + +static unsigned int msm_geni_serial_tx_empty(struct uart_port *uport) +{ + unsigned int tx_fifo_status; + unsigned int is_tx_empty = 1; + struct msm_geni_serial_port *port = GET_DEV_PORT(uport); + + if (!uart_console(uport) && device_pending_suspend(uport)) + return 1; + + if (port->xfer_mode == SE_DMA) + tx_fifo_status = port->tx_dma ? 1 : 0; + else + tx_fifo_status = geni_read_reg_nolog(uport->membase, + SE_GENI_TX_FIFO_STATUS); + if (tx_fifo_status) + is_tx_empty = 0; + + return is_tx_empty; +} + +static ssize_t xfer_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct msm_geni_serial_port *port = platform_get_drvdata(pdev); + ssize_t ret = 0; + + if (port->xfer_mode == FIFO_MODE) + ret = scnprintf(buf, PAGE_SIZE, "FIFO\n"); + else if (port->xfer_mode == SE_DMA) + ret = scnprintf(buf, PAGE_SIZE, "SE_DMA\n"); + + return ret; +} + +static ssize_t xfer_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct platform_device *pdev = to_platform_device(dev); + struct msm_geni_serial_port *port = platform_get_drvdata(pdev); + struct uart_port *uport = &port->uport; + int xfer_mode = port->xfer_mode; + unsigned long flags; + + if (uart_console(uport)) + return -EOPNOTSUPP; + + if (strnstr(buf, "FIFO", strlen("FIFO"))) { + xfer_mode = FIFO_MODE; + } else if (strnstr(buf, "SE_DMA", strlen("SE_DMA"))) { + xfer_mode = SE_DMA; + } else { + dev_err(dev, "%s: Invalid input %s\n", __func__, buf); + return -EINVAL; + } + + if (xfer_mode == port->xfer_mode) + return size; + + msm_geni_serial_power_on(uport); + spin_lock_irqsave(&uport->lock, flags); + msm_geni_serial_stop_tx(uport); + msm_geni_serial_stop_rx(uport); + port->xfer_mode = xfer_mode; + geni_se_select_mode(uport->membase, port->xfer_mode); + spin_unlock_irqrestore(&uport->lock, flags); + msm_geni_serial_start_rx(uport); + msm_geni_serial_power_off(uport); + + return size; +} + +static DEVICE_ATTR_RW(xfer_mode); + +#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(CONFIG_CONSOLE_POLL) +static int __init msm_geni_console_setup(struct console *co, char *options) +{ + struct uart_port *uport; + struct msm_geni_serial_port *dev_port; + int baud = 115200; + int bits = 8; + int parity = 'n'; + int flow = 'n'; + int ret = 0; + + if (unlikely(co->index >= GENI_UART_NR_PORTS || co->index < 0)) + return -ENXIO; + + dev_port = get_port_from_line(co->index, true); + if (IS_ERR_OR_NULL(dev_port)) { + ret = PTR_ERR(dev_port); + pr_err("Invalid line %d(%d)\n", co->index, ret); + return ret; + } + + uport = &dev_port->uport; + + if (unlikely(!uport->membase)) + return -ENXIO; + + if (se_geni_resources_on(&dev_port->serial_rsc)) + WARN_ON(1); + + if (unlikely(get_se_proto(uport->membase) != UART)) { + se_geni_resources_off(&dev_port->serial_rsc); + return -ENXIO; + } + + if (!dev_port->port_setup) { + msm_geni_serial_stop_rx(uport); + msm_geni_serial_port_setup(uport); + } + + if (options) + uart_parse_options(options, &baud, &parity, &bits, &flow); + + return uart_set_options(uport, co, baud, parity, bits, flow); +} + +static void +msm_geni_serial_early_console_write(struct console *con, const char *s, + unsigned int n) +{ + struct earlycon_device *dev = con->data; + + __msm_geni_serial_console_write(&dev->port, s, n); +} + +static int __init +msm_geni_serial_earlycon_setup(struct earlycon_device *dev, + const char *opt) +{ + struct uart_port *uport = &dev->port; + int ret = 0; + u32 tx_trans_cfg = 0; + u32 tx_parity_cfg = 0; + u32 rx_trans_cfg = 0; + u32 rx_parity_cfg = 0; + u32 stop_bit = 0; + u32 rx_stale = 0; + u32 bits_per_char = 0; + u32 s_clk_cfg = 0; + u32 baud = 115200; + int clk_div; + unsigned long clk_rate; + unsigned long cfg0, cfg1; + + if (!uport->membase) { + ret = -ENOMEM; + goto exit_geni_serial_earlyconsetup; + } + + if (get_se_proto(uport->membase) != UART) { + ret = -ENXIO; + goto exit_geni_serial_earlyconsetup; + } + + /* + * Ignore Flow control. + * Disable Tx Parity. + * Don't check Parity during Rx. + * Disable Rx Parity. + * n = 8. + * Stop bit = 0. + * Stale timeout in bit-time (3 chars worth). + */ + tx_trans_cfg |= UART_CTS_MASK; + tx_parity_cfg = 0; + rx_trans_cfg = 0; + rx_parity_cfg = 0; + bits_per_char = 0x8; + stop_bit = 0; + rx_stale = 0x18; + clk_div = get_clk_div_rate(baud, &clk_rate); + if (clk_div <= 0) { + ret = -EINVAL; + goto exit_geni_serial_earlyconsetup; + } + + s_clk_cfg |= SER_CLK_EN; + s_clk_cfg |= (clk_div << CLK_DIV_SHFT); + + /* + * Make an unconditional cancel on the main sequencer to reset + * it else we could end up in data loss scenarios. + */ + msm_geni_serial_poll_cancel_tx(uport); + msm_geni_serial_abort_rx(uport); + + geni_write_reg_nolog(0x21, uport->membase, GENI_SER_M_CLK_CFG); + geni_write_reg_nolog(0x21, uport->membase, GENI_SER_S_CLK_CFG); + geni_read_reg_nolog(uport->membase, GENI_SER_M_CLK_CFG); + + se_get_packing_config(8, 1, false, &cfg0, &cfg1); + geni_se_init(uport->membase, (DEF_FIFO_DEPTH_WORDS >> 1), + (DEF_FIFO_DEPTH_WORDS - 2)); + geni_se_select_mode(uport->membase, FIFO_MODE); + geni_write_reg_nolog(cfg0, uport->membase, SE_GENI_TX_PACKING_CFG0); + geni_write_reg_nolog(cfg1, uport->membase, SE_GENI_TX_PACKING_CFG1); + geni_write_reg_nolog(tx_trans_cfg, uport->membase, + SE_UART_TX_TRANS_CFG); + geni_write_reg_nolog(tx_parity_cfg, uport->membase, + SE_UART_TX_PARITY_CFG); + geni_write_reg_nolog(rx_trans_cfg, uport->membase, + SE_UART_RX_TRANS_CFG); + geni_write_reg_nolog(rx_parity_cfg, uport->membase, + SE_UART_RX_PARITY_CFG); + geni_write_reg_nolog(bits_per_char, uport->membase, + SE_UART_TX_WORD_LEN); + geni_write_reg_nolog(bits_per_char, uport->membase, + SE_UART_RX_WORD_LEN); + geni_write_reg_nolog(stop_bit, uport->membase, SE_UART_TX_STOP_BIT_LEN); + geni_write_reg_nolog(s_clk_cfg, uport->membase, GENI_SER_M_CLK_CFG); + geni_write_reg_nolog(s_clk_cfg, uport->membase, GENI_SER_S_CLK_CFG); + geni_read_reg_nolog(uport->membase, GENI_SER_M_CLK_CFG); + + dev->con->write = msm_geni_serial_early_console_write; + dev->con->setup = NULL; + /* + * Ensure that the early console setup completes before + * returning. + */ + mb(); +exit_geni_serial_earlyconsetup: + return ret; +} +OF_EARLYCON_DECLARE(msm_geni_serial, "qcom,msm-geni-console", + msm_geni_serial_earlycon_setup); + +static int console_register(struct uart_driver *drv) +{ + return uart_register_driver(drv); +} +static void console_unregister(struct uart_driver *drv) +{ + uart_unregister_driver(drv); +} + +static struct console cons_ops = { + .name = "ttyMSM", + .write = msm_geni_serial_console_write, + .device = uart_console_device, + .setup = msm_geni_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, + .data = &msm_geni_console_driver, +}; + +static struct uart_driver msm_geni_console_driver = { + .owner = THIS_MODULE, + .driver_name = "msm_geni_console", + .dev_name = "ttyMSM", + .nr = GENI_UART_NR_PORTS, + .cons = &cons_ops, +}; +#else +static int console_register(struct uart_driver *drv) +{ + return 0; +} + +static void console_unregister(struct uart_driver *drv) +{ +} +#endif /* defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(CONFIG_CONSOLE_POLL) */ + +static void msm_geni_serial_debug_init(struct uart_port *uport, bool console) +{ + struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport); + + msm_port->dbg = debugfs_create_dir(dev_name(uport->dev), NULL); + if (IS_ERR_OR_NULL(msm_port->dbg)) + dev_err(uport->dev, "Failed to create dbg dir\n"); + + if (!console) { + char name[30]; + + memset(name, 0, sizeof(name)); + if (!msm_port->ipc_log_rx) { + scnprintf(name, sizeof(name), "%s%s", + dev_name(uport->dev), "_rx"); + msm_port->ipc_log_rx = ipc_log_context_create( + IPC_LOG_TX_RX_PAGES, name, 0); + if (!msm_port->ipc_log_rx) + dev_info(uport->dev, "Err in Rx IPC Log\n"); + } + memset(name, 0, sizeof(name)); + if (!msm_port->ipc_log_tx) { + scnprintf(name, sizeof(name), "%s%s", + dev_name(uport->dev), "_tx"); + msm_port->ipc_log_tx = ipc_log_context_create( + IPC_LOG_TX_RX_PAGES, name, 0); + if (!msm_port->ipc_log_tx) + dev_info(uport->dev, "Err in Tx IPC Log\n"); + } + memset(name, 0, sizeof(name)); + if (!msm_port->ipc_log_pwr) { + scnprintf(name, sizeof(name), "%s%s", + dev_name(uport->dev), "_pwr"); + msm_port->ipc_log_pwr = ipc_log_context_create( + IPC_LOG_PWR_PAGES, name, 0); + if (!msm_port->ipc_log_pwr) + dev_info(uport->dev, "Err in Pwr IPC Log\n"); + } + memset(name, 0, sizeof(name)); + if (!msm_port->ipc_log_misc) { + scnprintf(name, sizeof(name), "%s%s", + dev_name(uport->dev), "_misc"); + msm_port->ipc_log_misc = ipc_log_context_create( + IPC_LOG_MISC_PAGES, name, 0); + if (!msm_port->ipc_log_misc) + dev_info(uport->dev, "Err in Misc IPC Log\n"); + } + } +} + +static void msm_geni_serial_cons_pm(struct uart_port *uport, + unsigned int new_state, unsigned int old_state) +{ + struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport); + + if (unlikely(!uart_console(uport))) + return; + + if (new_state == UART_PM_STATE_ON && old_state == UART_PM_STATE_OFF) + se_geni_resources_on(&msm_port->serial_rsc); + else if (new_state == UART_PM_STATE_OFF && + old_state == UART_PM_STATE_ON) + se_geni_resources_off(&msm_port->serial_rsc); +} + +static const struct uart_ops msm_geni_console_pops = { + .tx_empty = msm_geni_serial_tx_empty, + .stop_tx = msm_geni_serial_stop_tx, + .start_tx = msm_geni_serial_start_tx, + .stop_rx = msm_geni_serial_stop_rx, + .set_termios = msm_geni_serial_set_termios, + .startup = msm_geni_serial_startup, + .config_port = msm_geni_serial_config_port, + .shutdown = msm_geni_serial_shutdown, + .type = msm_geni_serial_get_type, + .set_mctrl = msm_geni_cons_set_mctrl, + .get_mctrl = msm_geni_cons_get_mctrl, +#ifdef CONFIG_CONSOLE_POLL + .poll_get_char = msm_geni_serial_get_char, + .poll_put_char = msm_geni_serial_poll_put_char, +#endif + .pm = msm_geni_serial_cons_pm, +}; + +static const struct uart_ops msm_geni_serial_pops = { + .tx_empty = msm_geni_serial_tx_empty, + .stop_tx = msm_geni_serial_stop_tx, + .start_tx = msm_geni_serial_start_tx, + .stop_rx = msm_geni_serial_stop_rx, + .set_termios = msm_geni_serial_set_termios, + .startup = msm_geni_serial_startup, + .config_port = msm_geni_serial_config_port, + .shutdown = msm_geni_serial_shutdown, + .type = msm_geni_serial_get_type, + .set_mctrl = msm_geni_serial_set_mctrl, + .get_mctrl = msm_geni_serial_get_mctrl, + .break_ctl = msm_geni_serial_break_ctl, + .flush_buffer = NULL, + .ioctl = msm_geni_serial_ioctl, +}; + +static const struct of_device_id msm_geni_device_tbl[] = { +#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(CONFIG_CONSOLE_POLL) + { .compatible = "qcom,msm-geni-console", + .data = (void *)&msm_geni_console_driver}, +#endif + { .compatible = "qcom,msm-geni-serial-hs", + .data = (void *)&msm_geni_serial_hs_driver}, + {}, +}; + +static int msm_geni_serial_probe(struct platform_device *pdev) +{ + int ret = 0; + int line; + struct msm_geni_serial_port *dev_port; + struct uart_port *uport; + struct resource *res; + struct uart_driver *drv; + const struct of_device_id *id; + bool is_console = false; + struct platform_device *wrapper_pdev; + struct device_node *wrapper_ph_node; + u32 wake_char = 0; + + id = of_match_device(msm_geni_device_tbl, &pdev->dev); + if (id) { + dev_dbg(&pdev->dev, "%s: %s\n", __func__, id->compatible); + drv = (struct uart_driver *)id->data; + } else { + dev_err(&pdev->dev, "%s: No matching device found\n", + __func__); + return -ENODEV; + } + + if (pdev->dev.of_node) { + if (drv->cons) + line = of_alias_get_id(pdev->dev.of_node, "serial"); + else + line = of_alias_get_id(pdev->dev.of_node, "hsuart"); + } else { + line = pdev->id; + } + + if (line < 0) + line = atomic_inc_return(&uart_line_id) - 1; + + if ((line < 0) || (line >= GENI_UART_NR_PORTS)) + return -ENXIO; + is_console = (drv->cons ? true : false); + dev_port = get_port_from_line(line, is_console); + if (IS_ERR_OR_NULL(dev_port)) { + ret = PTR_ERR(dev_port); + dev_err(&pdev->dev, "Invalid line %d(%d)\n", + line, ret); + goto exit_geni_serial_probe; + } + + uport = &dev_port->uport; + + /* Don't allow 2 drivers to access the same port */ + if (uport->private_data) { + ret = -ENODEV; + goto exit_geni_serial_probe; + } + + uport->dev = &pdev->dev; + + wrapper_ph_node = of_parse_phandle(pdev->dev.of_node, + "qcom,wrapper-core", 0); + if (IS_ERR_OR_NULL(wrapper_ph_node)) { + ret = PTR_ERR(wrapper_ph_node); + goto exit_geni_serial_probe; + } + wrapper_pdev = of_find_device_by_node(wrapper_ph_node); + of_node_put(wrapper_ph_node); + if (IS_ERR_OR_NULL(wrapper_pdev)) { + ret = PTR_ERR(wrapper_pdev); + goto exit_geni_serial_probe; + } + dev_port->wrapper_dev = &wrapper_pdev->dev; + dev_port->serial_rsc.wrapper_dev = &wrapper_pdev->dev; + + if (is_console) + ret = geni_se_resources_init(&dev_port->serial_rsc, + UART_CONSOLE_CORE2X_VOTE, + (DEFAULT_SE_CLK * DEFAULT_BUS_WIDTH)); + else + ret = geni_se_resources_init(&dev_port->serial_rsc, + UART_CORE2X_VOTE, + (DEFAULT_SE_CLK * DEFAULT_BUS_WIDTH)); + + if (ret) + goto exit_geni_serial_probe; + + dev_port->serial_rsc.ctrl_dev = &pdev->dev; + + if (of_property_read_u32(pdev->dev.of_node, "qcom,wakeup-byte", + &wake_char)) { + dev_dbg(&pdev->dev, "No Wakeup byte specified\n"); + } else { + dev_port->wakeup_byte = (u8)wake_char; + dev_info(&pdev->dev, "Wakeup byte 0x%x\n", + dev_port->wakeup_byte); + } + + dev_port->serial_rsc.se_clk = devm_clk_get(&pdev->dev, "se-clk"); + if (IS_ERR(dev_port->serial_rsc.se_clk)) { + ret = PTR_ERR(dev_port->serial_rsc.se_clk); + dev_err(&pdev->dev, "Err getting SE Core clk %d\n", ret); + goto exit_geni_serial_probe; + } + + dev_port->serial_rsc.m_ahb_clk = devm_clk_get(&pdev->dev, "m-ahb"); + if (IS_ERR(dev_port->serial_rsc.m_ahb_clk)) { + ret = PTR_ERR(dev_port->serial_rsc.m_ahb_clk); + dev_err(&pdev->dev, "Err getting M AHB clk %d\n", ret); + goto exit_geni_serial_probe; + } + + dev_port->serial_rsc.s_ahb_clk = devm_clk_get(&pdev->dev, "s-ahb"); + if (IS_ERR(dev_port->serial_rsc.s_ahb_clk)) { + ret = PTR_ERR(dev_port->serial_rsc.s_ahb_clk); + dev_err(&pdev->dev, "Err getting S AHB clk %d\n", ret); + goto exit_geni_serial_probe; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "se_phys"); + if (!res) { + ret = -ENXIO; + dev_err(&pdev->dev, "Err getting IO region\n"); + goto exit_geni_serial_probe; + } + + uport->mapbase = res->start; + uport->membase = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!uport->membase) { + ret = -ENOMEM; + dev_err(&pdev->dev, "Err IO mapping serial iomem\n"); + + goto exit_geni_serial_probe; + } + + /* Optional to use the Rx pin as wakeup irq */ + dev_port->wakeup_irq = platform_get_irq(pdev, 1); + if ((dev_port->wakeup_irq < 0 && !is_console)) + dev_info(&pdev->dev, "No wakeup IRQ configured\n"); + + dev_port->serial_rsc.geni_pinctrl = devm_pinctrl_get(&pdev->dev); + if (IS_ERR_OR_NULL(dev_port->serial_rsc.geni_pinctrl)) { + dev_err(&pdev->dev, "No pinctrl config specified!\n"); + ret = PTR_ERR(dev_port->serial_rsc.geni_pinctrl); + goto exit_geni_serial_probe; + } + dev_port->serial_rsc.geni_gpio_active = + pinctrl_lookup_state(dev_port->serial_rsc.geni_pinctrl, + PINCTRL_DEFAULT); + if (IS_ERR_OR_NULL(dev_port->serial_rsc.geni_gpio_active)) { + dev_err(&pdev->dev, "No default config specified!\n"); + ret = PTR_ERR(dev_port->serial_rsc.geni_gpio_active); + goto exit_geni_serial_probe; + } + + /* + * For clients who setup an Inband wakeup, leave the GPIO pins + * always connected to the core, else move the pins to their + * defined "sleep" state. + */ + if (dev_port->wakeup_irq > 0) { + dev_port->serial_rsc.geni_gpio_sleep = + dev_port->serial_rsc.geni_gpio_active; + } else { + dev_port->serial_rsc.geni_gpio_sleep = + pinctrl_lookup_state(dev_port->serial_rsc.geni_pinctrl, + PINCTRL_SLEEP); + if (IS_ERR_OR_NULL(dev_port->serial_rsc.geni_gpio_sleep)) { + dev_err(&pdev->dev, "No sleep config specified!\n"); + ret = PTR_ERR(dev_port->serial_rsc.geni_gpio_sleep); + goto exit_geni_serial_probe; + } + } + + wakeup_source_init(&dev_port->geni_wake, dev_name(&pdev->dev)); + dev_port->tx_fifo_depth = DEF_FIFO_DEPTH_WORDS; + dev_port->rx_fifo_depth = DEF_FIFO_DEPTH_WORDS; + dev_port->tx_fifo_width = DEF_FIFO_WIDTH_BITS; + uport->fifosize = + ((dev_port->tx_fifo_depth * dev_port->tx_fifo_width) >> 3); + + uport->irq = platform_get_irq(pdev, 0); + if (uport->irq < 0) { + ret = uport->irq; + dev_err(&pdev->dev, "Failed to get IRQ %d\n", ret); + goto exit_geni_serial_probe; + } + + uport->private_data = (void *)drv; + platform_set_drvdata(pdev, dev_port); + if (is_console) { + dev_port->handle_rx = handle_rx_console; + dev_port->rx_fifo = devm_kzalloc(uport->dev, sizeof(u32), + GFP_KERNEL); + } else { + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_set_autosuspend_delay(&pdev->dev, 150); + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_enable(&pdev->dev); + } + + se_geni_clks_on(&dev_port->serial_rsc); + geni_write_reg_nolog(0x21, uport->membase, GENI_SER_M_CLK_CFG); + geni_write_reg_nolog(0x21, uport->membase, GENI_SER_S_CLK_CFG); + geni_read_reg_nolog(uport->membase, GENI_SER_M_CLK_CFG); + se_geni_clks_off(&dev_port->serial_rsc); + + dev_info(&pdev->dev, "Serial port%d added.FifoSize %d is_console%d\n", + line, uport->fifosize, is_console); + device_create_file(uport->dev, &dev_attr_loopback); + device_create_file(uport->dev, &dev_attr_xfer_mode); + msm_geni_serial_debug_init(uport, is_console); + dev_port->port_setup = false; + return uart_add_one_port(drv, uport); + +exit_geni_serial_probe: + return ret; +} + +static int msm_geni_serial_remove(struct platform_device *pdev) +{ + struct msm_geni_serial_port *port = platform_get_drvdata(pdev); + struct uart_driver *drv = + (struct uart_driver *)port->uport.private_data; + + wakeup_source_remove(&port->geni_wake); + uart_remove_one_port(drv, &port->uport); + return 0; +} + + +#ifdef CONFIG_PM +static int msm_geni_serial_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct msm_geni_serial_port *port = platform_get_drvdata(pdev); + int ret = 0; + u32 geni_status = geni_read_reg_nolog(port->uport.membase, + SE_GENI_STATUS); + + wait_for_transfers_inflight(&port->uport); + /* + * Disable Interrupt + * Manual RFR On. + * Stop Rx. + * Resources off + */ + disable_irq(port->uport.irq); + stop_rx_sequencer(&port->uport); + geni_status = geni_read_reg_nolog(port->uport.membase, SE_GENI_STATUS); + if ((geni_status & M_GENI_CMD_ACTIVE)) + stop_tx_sequencer(&port->uport); + ret = se_geni_resources_off(&port->serial_rsc); + if (ret) { + dev_err(dev, "%s: Error ret %d\n", __func__, ret); + goto exit_runtime_suspend; + } + if (port->wakeup_irq > 0) { + port->edge_count = 0; + enable_irq(port->wakeup_irq); + } + IPC_LOG_MSG(port->ipc_log_pwr, "%s:\n", __func__); + __pm_relax(&port->geni_wake); +exit_runtime_suspend: + return ret; +} + +static int msm_geni_serial_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct msm_geni_serial_port *port = platform_get_drvdata(pdev); + int ret = 0; + + /* + * Do an unconditional relax followed by a stay awake in case the + * wake source is activated by the wakeup isr. + */ + __pm_relax(&port->geni_wake); + __pm_stay_awake(&port->geni_wake); + if (port->wakeup_irq > 0) + disable_irq(port->wakeup_irq); + /* + * Resources On. + * Start Rx. + * Auto RFR. + * Enable IRQ. + */ + ret = se_geni_resources_on(&port->serial_rsc); + if (ret) { + dev_err(dev, "%s: Error ret %d\n", __func__, ret); + __pm_relax(&port->geni_wake); + goto exit_runtime_resume; + } + start_rx_sequencer(&port->uport); + /* Ensure that the Rx is running before enabling interrupts */ + mb(); + /* + * Do not enable irq before interrupt registration which happens + * at port open time. + */ + if (pm_runtime_enabled(dev) && port->xfer_mode != INVALID) + enable_irq(port->uport.irq); + IPC_LOG_MSG(port->ipc_log_pwr, "%s:\n", __func__); +exit_runtime_resume: + return ret; +} + +static int msm_geni_serial_sys_suspend_noirq(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct msm_geni_serial_port *port = platform_get_drvdata(pdev); + struct uart_port *uport = &port->uport; + + if (uart_console(uport)) { + uart_suspend_port((struct uart_driver *)uport->private_data, + uport); + } else { + struct uart_state *state = uport->state; + struct tty_port *tty_port = &state->port; + + mutex_lock(&tty_port->mutex); + if (!pm_runtime_status_suspended(dev)) { + dev_err(dev, "%s:Active userspace vote; ioctl_cnt %d\n", + __func__, port->ioctl_count); + IPC_LOG_MSG(port->ipc_log_pwr, + "%s:Active userspace vote; ioctl_cnt %d\n", + __func__, port->ioctl_count); + mutex_unlock(&tty_port->mutex); + return -EBUSY; + } + IPC_LOG_MSG(port->ipc_log_pwr, "%s\n", __func__); + mutex_unlock(&tty_port->mutex); + } + return 0; +} + +static int msm_geni_serial_sys_resume_noirq(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct msm_geni_serial_port *port = platform_get_drvdata(pdev); + struct uart_port *uport = &port->uport; + + if (uart_console(uport) && + console_suspend_enabled && uport->suspended) { + uart_resume_port((struct uart_driver *)uport->private_data, + uport); + disable_irq(uport->irq); + } + return 0; +} +#else +static int msm_geni_serial_runtime_suspend(struct device *dev) +{ + return 0; +} + +static int msm_geni_serial_runtime_resume(struct device *dev) +{ + return 0; +} + +static int msm_geni_serial_sys_suspend_noirq(struct device *dev) +{ + return 0; +} + +static int msm_geni_serial_sys_resume_noirq(struct device *dev) +{ + return 0; +} +#endif + +static const struct dev_pm_ops msm_geni_serial_pm_ops = { + .runtime_suspend = msm_geni_serial_runtime_suspend, + .runtime_resume = msm_geni_serial_runtime_resume, + .suspend_noirq = msm_geni_serial_sys_suspend_noirq, + .resume_noirq = msm_geni_serial_sys_resume_noirq, +}; + +static struct platform_driver msm_geni_serial_platform_driver = { + .remove = msm_geni_serial_remove, + .probe = msm_geni_serial_probe, + .driver = { + .name = "msm_geni_serial", + .of_match_table = msm_geni_device_tbl, + .pm = &msm_geni_serial_pm_ops, + }, +}; + + +static struct uart_driver msm_geni_serial_hs_driver = { + .owner = THIS_MODULE, + .driver_name = "msm_geni_serial_hs", + .dev_name = "ttyHS", + .nr = GENI_UART_NR_PORTS, +}; + +static int __init msm_geni_serial_init(void) +{ + int ret = 0; + int i; + + for (i = 0; i < GENI_UART_NR_PORTS; i++) { + msm_geni_serial_ports[i].uport.iotype = UPIO_MEM; + msm_geni_serial_ports[i].uport.ops = &msm_geni_serial_pops; + msm_geni_serial_ports[i].uport.flags = UPF_BOOT_AUTOCONF; + msm_geni_serial_ports[i].uport.line = i; + } + + for (i = 0; i < GENI_UART_CONS_PORTS; i++) { + msm_geni_console_port.uport.iotype = UPIO_MEM; + msm_geni_console_port.uport.ops = &msm_geni_console_pops; + msm_geni_console_port.uport.flags = UPF_BOOT_AUTOCONF; + msm_geni_console_port.uport.line = i; + } + + ret = console_register(&msm_geni_console_driver); + if (ret) + return ret; + + ret = uart_register_driver(&msm_geni_serial_hs_driver); + if (ret) { + uart_unregister_driver(&msm_geni_console_driver); + return ret; + } + + ret = platform_driver_register(&msm_geni_serial_platform_driver); + if (ret) { + console_unregister(&msm_geni_console_driver); + uart_unregister_driver(&msm_geni_serial_hs_driver); + return ret; + } + + pr_info("%s: Driver initialized\n", __func__); + + return ret; +} +module_init(msm_geni_serial_init); + +static void __exit msm_geni_serial_exit(void) +{ + platform_driver_unregister(&msm_geni_serial_platform_driver); + uart_unregister_driver(&msm_geni_serial_hs_driver); + console_unregister(&msm_geni_console_driver); +} +module_exit(msm_geni_serial_exit); + +MODULE_DESCRIPTION("Serial driver for GENI based QTI serial cores"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("tty:msm_geni_geni_serial"); diff --git a/include/linux/msm-geni-se.h b/include/linux/msm-geni-se.h new file mode 100644 index 000000000000..2082e28db5cd --- /dev/null +++ b/include/linux/msm-geni-se.h @@ -0,0 +1,985 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved. + */ + +#ifndef _LINUX_MSM_GENI_SE +#define _LINUX_MSM_GENI_SE +#include +#include +#include +#include + +/* Transfer mode supported by GENI Serial Engines */ +enum se_xfer_mode { + INVALID, + FIFO_MODE, + GSI_DMA, + SE_DMA, +}; + +/* Protocols supported by GENI Serial Engines */ +enum se_protocol_types { + NONE, + SPI, + UART, + I2C, + I3C +}; + +/** + * struct geni_se_rsc - GENI Serial Engine Resource + * @ctrl_dev Pointer to controller device. + * @wrapper_dev: Pointer to the parent QUPv3 core. + * @se_clk: Handle to the core serial engine clock. + * @m_ahb_clk: Handle to the primary AHB clock. + * @s_ahb_clk: Handle to the secondary AHB clock. + * @ab_list: List Head of Average bus banwidth list. + * @ab_list_noc: List Head of Average DDR path bus + bandwidth list. + * @ab: Average bus bandwidth request value. + * @ab_noc: Average DDR path bus bandwidth request value. + * @ib_list: List Head of Instantaneous bus banwidth list. + * @ib_list_noc: List Head of Instantaneous DDR path bus + bandwidth list. + * @ib: Instantaneous bus bandwidth request value. + * @ib_noc: Instantaneous DDR path bus bandwidth + request value. + * @geni_pinctrl: Handle to the pinctrl configuration. + * @geni_gpio_active: Handle to the default/active pinctrl state. + * @geni_gpi_sleep: Handle to the sleep pinctrl state. + */ +struct se_geni_rsc { + struct device *ctrl_dev; + struct device *wrapper_dev; + struct clk *se_clk; + struct clk *m_ahb_clk; + struct clk *s_ahb_clk; + struct list_head ab_list; + struct list_head ab_list_noc; + unsigned long ab; + unsigned long ab_noc; + struct list_head ib_list; + struct list_head ib_list_noc; + unsigned long ib; + unsigned long ib_noc; + struct pinctrl *geni_pinctrl; + struct pinctrl_state *geni_gpio_active; + struct pinctrl_state *geni_gpio_sleep; + int clk_freq_out; +}; + +#define PINCTRL_DEFAULT "default" +#define PINCTRL_SLEEP "sleep" + +#define KHz(freq) (1000 * (freq)) + +/* Common SE registers */ +#define GENI_INIT_CFG_REVISION (0x0) +#define GENI_S_INIT_CFG_REVISION (0x4) +#define GENI_FORCE_DEFAULT_REG (0x20) +#define GENI_OUTPUT_CTRL (0x24) +#define GENI_CGC_CTRL (0x28) +#define SE_GENI_STATUS (0x40) +#define GENI_SER_M_CLK_CFG (0x48) +#define GENI_SER_S_CLK_CFG (0x4C) +#define GENI_CLK_CTRL_RO (0x60) +#define GENI_IF_FIFO_DISABLE_RO (0x64) +#define GENI_FW_REVISION_RO (0x68) +#define GENI_FW_S_REVISION_RO (0x6C) +#define SE_GENI_CLK_SEL (0x7C) +#define SE_GENI_BYTE_GRAN (0x254) +#define SE_GENI_DMA_MODE_EN (0x258) +#define SE_GENI_TX_PACKING_CFG0 (0x260) +#define SE_GENI_TX_PACKING_CFG1 (0x264) +#define SE_GENI_RX_PACKING_CFG0 (0x284) +#define SE_GENI_RX_PACKING_CFG1 (0x288) +#define SE_GENI_M_CMD0 (0x600) +#define SE_GENI_M_CMD_CTRL_REG (0x604) +#define SE_GENI_M_IRQ_STATUS (0x610) +#define SE_GENI_M_IRQ_EN (0x614) +#define SE_GENI_M_IRQ_CLEAR (0x618) +#define SE_GENI_S_CMD0 (0x630) +#define SE_GENI_S_CMD_CTRL_REG (0x634) +#define SE_GENI_S_IRQ_STATUS (0x640) +#define SE_GENI_S_IRQ_EN (0x644) +#define SE_GENI_S_IRQ_CLEAR (0x648) +#define SE_GENI_TX_FIFOn (0x700) +#define SE_GENI_RX_FIFOn (0x780) +#define SE_GENI_TX_FIFO_STATUS (0x800) +#define SE_GENI_RX_FIFO_STATUS (0x804) +#define SE_GENI_TX_WATERMARK_REG (0x80C) +#define SE_GENI_RX_WATERMARK_REG (0x810) +#define SE_GENI_RX_RFR_WATERMARK_REG (0x814) +#define SE_GENI_IOS (0x908) +#define SE_GENI_M_GP_LENGTH (0x910) +#define SE_GENI_S_GP_LENGTH (0x914) +#define GENI_I3C_IBI_LEGACY (0xA9c) +#define SE_GSI_EVENT_EN (0xE18) +#define SE_IRQ_EN (0xE1C) +#define SE_HW_PARAM_0 (0xE24) +#define SE_HW_PARAM_1 (0xE28) +#define SE_DMA_GENERAL_CFG (0xE30) +#define SE_DMA_DEBUG_REG0 (0xE40) + +/* GENI_OUTPUT_CTRL fields */ +#define DEFAULT_IO_OUTPUT_CTRL_MSK (GENMASK(6, 0)) + +/* GENI_FORCE_DEFAULT_REG fields */ +#define FORCE_DEFAULT (BIT(0)) + +/* GENI_CGC_CTRL fields */ +#define CFG_AHB_CLK_CGC_ON (BIT(0)) +#define CFG_AHB_WR_ACLK_CGC_ON (BIT(1)) +#define DATA_AHB_CLK_CGC_ON (BIT(2)) +#define SCLK_CGC_ON (BIT(3)) +#define TX_CLK_CGC_ON (BIT(4)) +#define RX_CLK_CGC_ON (BIT(5)) +#define EXT_CLK_CGC_ON (BIT(6)) +#define PROG_RAM_HCLK_OFF (BIT(8)) +#define PROG_RAM_SCLK_OFF (BIT(9)) +#define DEFAULT_CGC_EN (GENMASK(6, 0)) + +/* GENI_STATUS fields */ +#define M_GENI_CMD_ACTIVE (BIT(0)) +#define S_GENI_CMD_ACTIVE (BIT(12)) + +/* GENI_SER_M_CLK_CFG/GENI_SER_S_CLK_CFG */ +#define SER_CLK_EN (BIT(0)) +#define CLK_DIV_MSK (GENMASK(15, 4)) +#define CLK_DIV_SHFT (4) + +/* CLK_CTRL_RO fields */ + +/* FIFO_IF_DISABLE_RO fields */ +#define FIFO_IF_DISABLE (BIT(0)) + +/* FW_REVISION_RO fields */ +#define FW_REV_PROTOCOL_MSK (GENMASK(15, 8)) +#define FW_REV_PROTOCOL_SHFT (8) +#define FW_REV_VERSION_MSK (GENMASK(7, 0)) + +/* GENI_CLK_SEL fields */ +#define CLK_SEL_MSK (GENMASK(2, 0)) + +/* SE_GENI_DMA_MODE_EN */ +#define GENI_DMA_MODE_EN (BIT(0)) + +/* GENI_M_CMD0 fields */ +#define M_OPCODE_MSK (GENMASK(31, 27)) +#define M_OPCODE_SHFT (27) +#define M_PARAMS_MSK (GENMASK(26, 0)) + +/* GENI_M_CMD_CTRL_REG */ +#define M_GENI_CMD_CANCEL BIT(2) +#define M_GENI_CMD_ABORT BIT(1) +#define M_GENI_DISABLE BIT(0) + +/* GENI_S_CMD0 fields */ +#define S_OPCODE_MSK (GENMASK(31, 27)) +#define S_OPCODE_SHFT (27) +#define S_PARAMS_MSK (GENMASK(26, 0)) + +/* GENI_S_CMD_CTRL_REG */ +#define S_GENI_CMD_CANCEL (BIT(2)) +#define S_GENI_CMD_ABORT (BIT(1)) +#define S_GENI_DISABLE (BIT(0)) + +/* GENI_M_IRQ_EN fields */ +#define M_CMD_DONE_EN (BIT(0)) +#define M_CMD_OVERRUN_EN (BIT(1)) +#define M_ILLEGAL_CMD_EN (BIT(2)) +#define M_CMD_FAILURE_EN (BIT(3)) +#define M_CMD_CANCEL_EN (BIT(4)) +#define M_CMD_ABORT_EN (BIT(5)) +#define M_TIMESTAMP_EN (BIT(6)) +#define M_RX_IRQ_EN (BIT(7)) +#define M_GP_SYNC_IRQ_0_EN (BIT(8)) +#define M_GP_IRQ_0_EN (BIT(9)) +#define M_GP_IRQ_1_EN (BIT(10)) +#define M_GP_IRQ_2_EN (BIT(11)) +#define M_GP_IRQ_3_EN (BIT(12)) +#define M_GP_IRQ_4_EN (BIT(13)) +#define M_GP_IRQ_5_EN (BIT(14)) +#define M_IO_DATA_DEASSERT_EN (BIT(22)) +#define M_IO_DATA_ASSERT_EN (BIT(23)) +#define M_RX_FIFO_RD_ERR_EN (BIT(24)) +#define M_RX_FIFO_WR_ERR_EN (BIT(25)) +#define M_RX_FIFO_WATERMARK_EN (BIT(26)) +#define M_RX_FIFO_LAST_EN (BIT(27)) +#define M_TX_FIFO_RD_ERR_EN (BIT(28)) +#define M_TX_FIFO_WR_ERR_EN (BIT(29)) +#define M_TX_FIFO_WATERMARK_EN (BIT(30)) +#define M_SEC_IRQ_EN (BIT(31)) +#define M_COMMON_GENI_M_IRQ_EN (GENMASK(6, 1) | \ + M_IO_DATA_DEASSERT_EN | \ + M_IO_DATA_ASSERT_EN | M_RX_FIFO_RD_ERR_EN | \ + M_RX_FIFO_WR_ERR_EN | M_TX_FIFO_RD_ERR_EN | \ + M_TX_FIFO_WR_ERR_EN) + +/* GENI_S_IRQ_EN fields */ +#define S_CMD_DONE_EN (BIT(0)) +#define S_CMD_OVERRUN_EN (BIT(1)) +#define S_ILLEGAL_CMD_EN (BIT(2)) +#define S_CMD_FAILURE_EN (BIT(3)) +#define S_CMD_CANCEL_EN (BIT(4)) +#define S_CMD_ABORT_EN (BIT(5)) +#define S_GP_SYNC_IRQ_0_EN (BIT(8)) +#define S_GP_IRQ_0_EN (BIT(9)) +#define S_GP_IRQ_1_EN (BIT(10)) +#define S_GP_IRQ_2_EN (BIT(11)) +#define S_GP_IRQ_3_EN (BIT(12)) +#define S_GP_IRQ_4_EN (BIT(13)) +#define S_GP_IRQ_5_EN (BIT(14)) +#define S_IO_DATA_DEASSERT_EN (BIT(22)) +#define S_IO_DATA_ASSERT_EN (BIT(23)) +#define S_RX_FIFO_RD_ERR_EN (BIT(24)) +#define S_RX_FIFO_WR_ERR_EN (BIT(25)) +#define S_RX_FIFO_WATERMARK_EN (BIT(26)) +#define S_RX_FIFO_LAST_EN (BIT(27)) +#define S_COMMON_GENI_S_IRQ_EN (GENMASK(5, 1) | GENMASK(13, 9) | \ + S_RX_FIFO_RD_ERR_EN | S_RX_FIFO_WR_ERR_EN) + +/* GENI_/TX/RX/RX_RFR/_WATERMARK_REG fields */ +#define WATERMARK_MSK (GENMASK(5, 0)) + +/* GENI_TX_FIFO_STATUS fields */ +#define TX_FIFO_WC (GENMASK(27, 0)) + +/* GENI_RX_FIFO_STATUS fields */ +#define RX_LAST (BIT(31)) +#define RX_LAST_BYTE_VALID_MSK (GENMASK(30, 28)) +#define RX_LAST_BYTE_VALID_SHFT (28) +#define RX_FIFO_WC_MSK (GENMASK(24, 0)) + +/* SE_GSI_EVENT_EN fields */ +#define DMA_RX_EVENT_EN (BIT(0)) +#define DMA_TX_EVENT_EN (BIT(1)) +#define GENI_M_EVENT_EN (BIT(2)) +#define GENI_S_EVENT_EN (BIT(3)) + +/* GENI_I3C_IBI_LEGACY fields */ +#define I3C_IBI_LEGACY_EN (BIT(0)) +#define I3C_IBI_LEGACY_PORTS_EN (BIT(1)) + +/* SE_GENI_IOS fields */ +#define IO2_DATA_IN (BIT(1)) +#define RX_DATA_IN (BIT(0)) + +/* SE_IRQ_EN fields */ +#define DMA_RX_IRQ_EN (BIT(0)) +#define DMA_TX_IRQ_EN (BIT(1)) +#define GENI_M_IRQ_EN (BIT(2)) +#define GENI_S_IRQ_EN (BIT(3)) + +/* SE_HW_PARAM_0 fields */ +#define TX_FIFO_WIDTH_MSK (GENMASK(29, 24)) +#define TX_FIFO_WIDTH_SHFT (24) +#define TX_FIFO_DEPTH_MSK (GENMASK(21, 16)) +#define TX_FIFO_DEPTH_SHFT (16) + +/* SE_HW_PARAM_1 fields */ +#define RX_FIFO_WIDTH_MSK (GENMASK(29, 24)) +#define RX_FIFO_WIDTH_SHFT (24) +#define RX_FIFO_DEPTH_MSK (GENMASK(21, 16)) +#define RX_FIFO_DEPTH_SHFT (16) + +/* SE_DMA_GENERAL_CFG */ +#define DMA_RX_CLK_CGC_ON (BIT(0)) +#define DMA_TX_CLK_CGC_ON (BIT(1)) +#define DMA_AHB_SLV_CFG_ON (BIT(2)) +#define AHB_SEC_SLV_CLK_CGC_ON (BIT(3)) +#define DUMMY_RX_NON_BUFFERABLE (BIT(4)) +#define RX_DMA_ZERO_PADDING_EN (BIT(5)) +#define RX_DMA_IRQ_DELAY_MSK (GENMASK(8, 6)) +#define RX_DMA_IRQ_DELAY_SHFT (6) + +#define SE_DMA_TX_PTR_L (0xC30) +#define SE_DMA_TX_PTR_H (0xC34) +#define SE_DMA_TX_ATTR (0xC38) +#define SE_DMA_TX_LEN (0xC3C) +#define SE_DMA_TX_IRQ_STAT (0xC40) +#define SE_DMA_TX_IRQ_CLR (0xC44) +#define SE_DMA_TX_IRQ_EN (0xC48) +#define SE_DMA_TX_IRQ_EN_SET (0xC4C) +#define SE_DMA_TX_IRQ_EN_CLR (0xC50) +#define SE_DMA_TX_LEN_IN (0xC54) +#define SE_DMA_TX_FSM_RST (0xC58) +#define SE_DMA_TX_MAX_BURST (0xC5C) + +#define SE_DMA_RX_PTR_L (0xD30) +#define SE_DMA_RX_PTR_H (0xD34) +#define SE_DMA_RX_ATTR (0xD38) +#define SE_DMA_RX_LEN (0xD3C) +#define SE_DMA_RX_IRQ_STAT (0xD40) +#define SE_DMA_RX_IRQ_CLR (0xD44) +#define SE_DMA_RX_IRQ_EN (0xD48) +#define SE_DMA_RX_IRQ_EN_SET (0xD4C) +#define SE_DMA_RX_IRQ_EN_CLR (0xD50) +#define SE_DMA_RX_LEN_IN (0xD54) +#define SE_DMA_RX_FSM_RST (0xD58) +#define SE_DMA_RX_MAX_BURST (0xD5C) +#define SE_DMA_RX_FLUSH (0xD60) + +/* SE_DMA_TX_IRQ_STAT Register fields */ +#define TX_DMA_DONE (BIT(0)) +#define TX_EOT (BIT(1)) +#define TX_SBE (BIT(2)) +#define TX_RESET_DONE (BIT(3)) + +/* SE_DMA_RX_IRQ_STAT Register fields */ +#define RX_DMA_DONE (BIT(0)) +#define RX_EOT (BIT(1)) +#define RX_SBE (BIT(2)) +#define RX_RESET_DONE (BIT(3)) +#define RX_FLUSH_DONE (BIT(4)) +#define RX_GENI_GP_IRQ (GENMASK(10, 5)) +#define RX_GENI_CANCEL_IRQ (BIT(11)) +#define RX_GENI_GP_IRQ_EXT (GENMASK(13, 12)) + +#define DEFAULT_BUS_WIDTH (4) +#define DEFAULT_SE_CLK (19200000) + +/* GSI TRE fields */ +/* Packing fields */ +#define GSI_TX_PACK_EN (BIT(0)) +#define GSI_RX_PACK_EN (BIT(1)) +#define GSI_PRESERVE_PACK (BIT(2)) + +#define GENI_SE_ERR(log_ctx, print, dev, x...) do { \ +if (log_ctx) \ + ipc_log_string(log_ctx, x); \ +if (print) { \ + if (dev) \ + dev_err((dev), x); \ + else \ + pr_err(x); \ +} \ +} while (0) + +#define GENI_SE_DBG(log_ctx, print, dev, x...) do { \ +if (log_ctx) \ + ipc_log_string(log_ctx, x); \ +if (print) { \ + if (dev) \ + dev_dbg((dev), x); \ + else \ + pr_debug(x); \ +} \ +} while (0) + + +#if IS_ENABLED(CONFIG_MSM_GENI_SE) +/** + * geni_read_reg_nolog() - Helper function to read from a GENI register + * @base: Base address of the serial engine's register block. + * @offset: Offset within the serial engine's register block. + * + * Return: Return the contents of the register. + */ +unsigned int geni_read_reg_nolog(void __iomem *base, int offset); + +/** + * geni_write_reg_nolog() - Helper function to write into a GENI register + * @value: Value to be written into the register. + * @base: Base address of the serial engine's register block. + * @offset: Offset within the serial engine's register block. + */ +void geni_write_reg_nolog(unsigned int value, void __iomem *base, int offset); + +/** + * geni_read_reg() - Helper function to read from a GENI register + * @base: Base address of the serial engine's register block. + * @offset: Offset within the serial engine's register block. + * + * Return: Return the contents of the register. + */ +unsigned int geni_read_reg(void __iomem *base, int offset); + +/** + * geni_write_reg() - Helper function to write into a GENI register + * @value: Value to be written into the register. + * @base: Base address of the serial engine's register block. + * @offset: Offset within the serial engine's register block. + */ +void geni_write_reg(unsigned int value, void __iomem *base, int offset); + +/** + * get_se_proto() - Read the protocol configured for a serial engine + * @base: Base address of the serial engine's register block. + * + * Return: Protocol value as configured in the serial engine. + */ +int get_se_proto(void __iomem *base); + +/** + * get_se_m_fw() - Read the Firmware ver for the Main sequencer engine + * @base: Base address of the serial engine's register block. + * + * Return: Firmware version for the Main sequencer engine + */ +int get_se_m_fw(void __iomem *base); + +/** + * get_se_s_fw() - Read the Firmware ver for the Secondry sequencer engine + * @base: Base address of the serial engine's register block. + * + * Return: Firmware version for the Secondry sequencer engine + */ +int get_se_s_fw(void __iomem *base); + +/** + * geni_se_init() - Initialize the GENI Serial Engine + * @base: Base address of the serial engine's register block. + * @rx_wm: Receive watermark to be configured. + * @rx_rfr_wm: Ready-for-receive watermark to be configured. + * + * This function is used to initialize the GENI serial engine, configure + * the transfer mode, receive watermark and ready-for-receive watermarks. + * + * Return: 0 on success, standard Linux error codes on failure/error. + */ +int geni_se_init(void __iomem *base, unsigned int rx_wm, unsigned int rx_rfr); + +/** + * geni_se_select_mode() - Select the serial engine transfer mode + * @base: Base address of the serial engine's register block. + * @mode: Transfer mode to be selected. + * + * Return: 0 on success, standard Linux error codes on failure. + */ +int geni_se_select_mode(void __iomem *base, int mode); + +/** + * geni_setup_m_cmd() - Setup the primary sequencer + * @base: Base address of the serial engine's register block. + * @cmd: Command/Operation to setup in the primary sequencer. + * @params: Parameter for the sequencer command. + * + * This function is used to configure the primary sequencer with the + * command and its assoicated parameters. + */ +void geni_setup_m_cmd(void __iomem *base, u32 cmd, u32 params); + +/** + * geni_setup_s_cmd() - Setup the secondary sequencer + * @base: Base address of the serial engine's register block. + * @cmd: Command/Operation to setup in the secondary sequencer. + * @params: Parameter for the sequencer command. + * + * This function is used to configure the secondary sequencer with the + * command and its assoicated parameters. + */ +void geni_setup_s_cmd(void __iomem *base, u32 cmd, u32 params); + +/** + * geni_cancel_m_cmd() - Cancel the command configured in the primary sequencer + * @base: Base address of the serial engine's register block. + * + * This function is used to cancel the currently configured command in the + * primary sequencer. + */ +void geni_cancel_m_cmd(void __iomem *base); + +/** + * geni_cancel_s_cmd() - Cancel the command configured in the secondary + * sequencer + * @base: Base address of the serial engine's register block. + * + * This function is used to cancel the currently configured command in the + * secondary sequencer. + */ +void geni_cancel_s_cmd(void __iomem *base); + +/** + * geni_abort_m_cmd() - Abort the command configured in the primary sequencer + * @base: Base address of the serial engine's register block. + * + * This function is used to force abort the currently configured command in the + * primary sequencer. + */ +void geni_abort_m_cmd(void __iomem *base); + +/** + * geni_abort_s_cmd() - Abort the command configured in the secondary + * sequencer + * @base: Base address of the serial engine's register block. + * + * This function is used to force abort the currently configured command in the + * secondary sequencer. + */ +void geni_abort_s_cmd(void __iomem *base); + +/** + * get_tx_fifo_depth() - Get the TX fifo depth of the serial engine + * @base: Base address of the serial engine's register block. + * + * This function is used to get the depth i.e. number of elements in the + * TX fifo of the serial engine. + * + * Return: TX fifo depth in units of FIFO words. + */ +int get_tx_fifo_depth(void __iomem *base); + +/** + * get_tx_fifo_width() - Get the TX fifo width of the serial engine + * @base: Base address of the serial engine's register block. + * + * This function is used to get the width i.e. word size per element in the + * TX fifo of the serial engine. + * + * Return: TX fifo width in bits. + */ +int get_tx_fifo_width(void __iomem *base); + +/** + * get_rx_fifo_depth() - Get the RX fifo depth of the serial engine + * @base: Base address of the serial engine's register block. + * + * This function is used to get the depth i.e. number of elements in the + * RX fifo of the serial engine. + * + * Return: RX fifo depth in units of FIFO words. + */ +int get_rx_fifo_depth(void __iomem *base); + +/** + * se_get_packing_config() - Get the packing configuration based on input + * @bpw: Bits of data per transfer word. + * @pack_words: Number of words per fifo element. + * @msb_to_lsb: Transfer from MSB to LSB or vice-versa. + * @cfg0: Output buffer to hold the first half of configuration. + * @cfg1: Output buffer to hold the second half of configuration. + * + * This function is used to calculate the packing configuration based on + * the input packing requirement and the configuration logic. + */ +void se_get_packing_config(int bpw, int pack_words, bool msb_to_lsb, + unsigned long *cfg0, unsigned long *cfg1); + +/** + * se_config_packing() - Packing configuration of the serial engine + * @base: Base address of the serial engine's register block. + * @bpw: Bits of data per transfer word. + * @pack_words: Number of words per fifo element. + * @msb_to_lsb: Transfer from MSB to LSB or vice-versa. + * + * This function is used to configure the packing rules for the current + * transfer. + */ +void se_config_packing(void __iomem *base, int bpw, int pack_words, + bool msb_to_lsb); + +/** + * se_geni_clks_off() - Turn off clocks associated with the serial + * engine + * @rsc: Handle to resources associated with the serial engine. + * + * Return: 0 on success, standard Linux error codes on failure/error. + */ +int se_geni_clks_off(struct se_geni_rsc *rsc); + +/** + * se_geni_clks_on() - Turn on clocks associated with the serial + * engine + * @rsc: Handle to resources associated with the serial engine. + * + * Return: 0 on success, standard Linux error codes on failure/error. + */ +int se_geni_clks_on(struct se_geni_rsc *rsc); + +/** + * se_geni_resources_off() - Turn off resources associated with the serial + * engine + * @rsc: Handle to resources associated with the serial engine. + * + * Return: 0 on success, standard Linux error codes on failure/error. + */ +int se_geni_resources_off(struct se_geni_rsc *rsc); + +/** + * se_geni_resources_on() - Turn on resources associated with the serial + * engine + * @rsc: Handle to resources associated with the serial engine. + * + * Return: 0 on success, standard Linux error codes on failure/error. + */ +int se_geni_resources_on(struct se_geni_rsc *rsc); + +/** + * geni_se_resources_init() - Init the SE resource structure + * @rsc: SE resource structure to be initialized. + * @ab: Initial Average bus bandwidth request value. + * @ib: Initial Instantaneous bus bandwidth request value. + * + * Return: 0 on success, standard Linux error codes on failure. + */ +int geni_se_resources_init(struct se_geni_rsc *rsc, + unsigned long ab, unsigned long ib); + +/** + * geni_se_clk_tbl_get() - Get the clock table to program DFS + * @rsc: Resource for which the clock table is requested. + * @tbl: Table in which the output is returned. + * + * This function is called by the protocol drivers to determine the different + * clock frequencies supported by Serail Engine Core Clock. The protocol + * drivers use the output to determine the clock frequency index to be + * programmed into DFS. + * + * Return: number of valid performance levels in the table on success, + * standard Linux error codes on failure. + */ +int geni_se_clk_tbl_get(struct se_geni_rsc *rsc, unsigned long **tbl); + +/** + * geni_se_clk_freq_match() - Get the matching or closest SE clock frequency + * @rsc: Resource for which the clock frequency is requested. + * @req_freq: Requested clock frequency. + * @index: Index of the resultant frequency in the table. + * @res_freq: Resultant frequency which matches or is closer to the + * requested frequency. + * @exact: Flag to indicate exact multiple requirement of the requested + * frequency . + * + * This function is called by the protocol drivers to determine the matching + * or closest frequency of the Serial Engine clock to be selected in order + * to meet the performance requirements. + * + * Return: 0 on success, standard Linux error codes on failure. + */ +int geni_se_clk_freq_match(struct se_geni_rsc *rsc, unsigned long req_freq, + unsigned int *index, unsigned long *res_freq, + bool exact); + +/** + * geni_se_tx_dma_prep() - Prepare the Serial Engine for TX DMA transfer + * @wrapper_dev: QUPv3 Wrapper Device to which the TX buffer is mapped. + * @base: Base address of the SE register block. + * @tx_buf: Pointer to the TX buffer. + * @tx_len: Length of the TX buffer. + * @tx_dma: Pointer to store the mapped DMA address. + * + * This function is used to prepare the buffers for DMA TX. + * + * Return: 0 on success, standard Linux error codes on error/failure. + */ +int geni_se_tx_dma_prep(struct device *wrapper_dev, void __iomem *base, + void *tx_buf, int tx_len, dma_addr_t *tx_dma); + +/** + * geni_se_rx_dma_prep() - Prepare the Serial Engine for RX DMA transfer + * @wrapper_dev: QUPv3 Wrapper Device to which the TX buffer is mapped. + * @base: Base address of the SE register block. + * @rx_buf: Pointer to the RX buffer. + * @rx_len: Length of the RX buffer. + * @rx_dma: Pointer to store the mapped DMA address. + * + * This function is used to prepare the buffers for DMA RX. + * + * Return: 0 on success, standard Linux error codes on error/failure. + */ +int geni_se_rx_dma_prep(struct device *wrapper_dev, void __iomem *base, + void *rx_buf, int rx_len, dma_addr_t *rx_dma); + +/** + * geni_se_tx_dma_unprep() - Unprepare the Serial Engine after TX DMA transfer + * @wrapper_dev: QUPv3 Wrapper Device to which the TX buffer is mapped. + * @tx_dma: DMA address of the TX buffer. + * @tx_len: Length of the TX buffer. + * + * This function is used to unprepare the DMA buffers after DMA TX. + */ +void geni_se_tx_dma_unprep(struct device *wrapper_dev, + dma_addr_t tx_dma, int tx_len); + +/** + * geni_se_rx_dma_unprep() - Unprepare the Serial Engine after RX DMA transfer + * @wrapper_dev: QUPv3 Wrapper Device to which the TX buffer is mapped. + * @rx_dma: DMA address of the RX buffer. + * @rx_len: Length of the RX buffer. + * + * This function is used to unprepare the DMA buffers after DMA RX. + */ +void geni_se_rx_dma_unprep(struct device *wrapper_dev, + dma_addr_t rx_dma, int rx_len); + +/** + * geni_se_qupv3_hw_version() - Read the QUPv3 Hardware version + * @wrapper_dev: Pointer to the corresponding QUPv3 wrapper core. + * @major: Buffer for Major Version field. + * @minor: Buffer for Minor Version field. + * @step: Buffer for Step Version field. + * + * Return: 0 on success, standard Linux error codes on failure/error. + */ +int geni_se_qupv3_hw_version(struct device *wrapper_dev, unsigned int *major, + unsigned int *minor, unsigned int *step); + +/** + * geni_se_iommu_map_buf() - Map a single buffer into QUPv3 context bank + * @wrapper_dev: Pointer to the corresponding QUPv3 wrapper core. + * @iova: Pointer in which the mapped virtual address is stored. + * @buf: Address of the buffer that needs to be mapped. + * @size: Size of the buffer. + * @dir: Direction of the DMA transfer. + * + * This function is used to map an already allocated buffer into the + * QUPv3 context bank device space. + * + * Return: 0 on success, standard Linux error codes on failure/error. + */ +int geni_se_iommu_map_buf(struct device *wrapper_dev, dma_addr_t *iova, + void *buf, size_t size, enum dma_data_direction dir); + +/** + * geni_se_iommu_alloc_buf() - Allocate & map a single buffer into QUPv3 + * context bank + * @wrapper_dev: Pointer to the corresponding QUPv3 wrapper core. + * @iova: Pointer in which the mapped virtual address is stored. + * @size: Size of the buffer. + * + * This function is used to allocate a buffer and map it into the + * QUPv3 context bank device space. + * + * Return: address of the buffer on success, NULL or ERR_PTR on + * failure/error. + */ +void *geni_se_iommu_alloc_buf(struct device *wrapper_dev, dma_addr_t *iova, + size_t size); + +/** + * geni_se_iommu_unmap_buf() - Unmap a single buffer from QUPv3 context bank + * @wrapper_dev: Pointer to the corresponding QUPv3 wrapper core. + * @iova: Pointer in which the mapped virtual address is stored. + * @size: Size of the buffer. + * @dir: Direction of the DMA transfer. + * + * This function is used to unmap an already mapped buffer from the + * QUPv3 context bank device space. + * + * Return: 0 on success, standard Linux error codes on failure/error. + */ +int geni_se_iommu_unmap_buf(struct device *wrapper_dev, dma_addr_t *iova, + size_t size, enum dma_data_direction dir); + +/** + * geni_se_iommu_free_buf() - Unmap & free a single buffer from QUPv3 + * context bank + * @wrapper_dev: Pointer to the corresponding QUPv3 wrapper core. + * @iova: Pointer in which the mapped virtual address is stored. + * @buf: Address of the buffer. + * @size: Size of the buffer. + * + * This function is used to unmap and free a buffer from the + * QUPv3 context bank device space. + * + * Return: 0 on success, standard Linux error codes on failure/error. + */ +int geni_se_iommu_free_buf(struct device *wrapper_dev, dma_addr_t *iova, + void *buf, size_t size); + + +/** + * geni_se_dump_dbg_regs() - Print relevant registers that capture most + * accurately the state of an SE; meant to be called + * in case of errors to help debug. + * @_dev: Pointer to the SE's device. + * @iomem: Base address of the SE's register space. + * @ipc: IPC log context handle. + * + * This function is used to print out all the registers that capture the state + * of an SE to help debug any errors. + * + * Return: None + */ +void geni_se_dump_dbg_regs(struct se_geni_rsc *rsc, void __iomem *base, + void *ipc); +#else +static inline unsigned int geni_read_reg_nolog(void __iomem *base, int offset) +{ + return 0; +} + +static inline void geni_write_reg_nolog(unsigned int value, + void __iomem *base, int offset) +{ +} + +static inline unsigned int geni_read_reg(void __iomem *base, int offset) +{ + return 0; +} + +static inline void geni_write_reg(unsigned int value, void __iomem *base, + int offset) +{ +} + +static inline int get_se_proto(void __iomem *base) +{ + return -ENXIO; +} + +static inline int geni_se_init(void __iomem *base, + unsigned int rx_wm, unsigned int rx_rfr) +{ + return -ENXIO; +} + +static inline int geni_se_select_mode(void __iomem *base, int mode) +{ + return -ENXIO; +} + +static inline void geni_setup_m_cmd(void __iomem *base, u32 cmd, + u32 params) +{ +} + +static inline void geni_setup_s_cmd(void __iomem *base, u32 cmd, + u32 params) +{ +} + +static inline void geni_cancel_m_cmd(void __iomem *base) +{ +} + +static inline void geni_cancel_s_cmd(void __iomem *base) +{ +} + +static inline void geni_abort_m_cmd(void __iomem *base) +{ +} + +static inline void geni_abort_s_cmd(void __iomem *base) +{ +} + +static inline int get_tx_fifo_depth(void __iomem *base) +{ + return -ENXIO; +} + +static inline int get_tx_fifo_width(void __iomem *base) +{ + return -ENXIO; +} + +static inline int get_rx_fifo_depth(void __iomem *base) +{ + return -ENXIO; +} + +static inline void se_get_packing_config(int bpw, int pack_words, + bool msb_to_lsb, unsigned long *cfg0, + unsigned long *cfg1) +{ +} + +static inline void se_config_packing(void __iomem *base, int bpw, + int pack_words, bool msb_to_lsb) +{ +} + +static inline int se_geni_clks_on(struct se_geni_rsc *rsc) +{ + return -ENXIO; +} + +static inline int se_geni_clks_off(struct se_geni_rsc *rsc) +{ + return -ENXIO; +} + +static inline int se_geni_resources_on(struct se_geni_rsc *rsc) +{ + return -ENXIO; +} + +static inline int se_geni_resources_off(struct se_geni_rsc *rsc) +{ + return -ENXIO; +} + +static inline int geni_se_resources_init(struct se_geni_rsc *rsc, + unsigned long ab, unsigned long ib) +{ + return -ENXIO; +} + +static inline int geni_se_clk_tbl_get(struct se_geni_rsc *rsc, + unsigned long **tbl) +{ + return -ENXIO; +} + +static inline int geni_se_clk_freq_match(struct se_geni_rsc *rsc, + unsigned long req_freq, unsigned int *index, + unsigned long *res_freq, bool exact) +{ + return -ENXIO; +} + +static inline int geni_se_tx_dma_prep(struct device *wrapper_dev, + void __iomem *base, void *tx_buf, int tx_len, dma_addr_t *tx_dma) +{ + return -ENXIO; +} + +static inline int geni_se_rx_dma_prep(struct device *wrapper_dev, + void __iomem *base, void *rx_buf, int rx_len, dma_addr_t *rx_dma) +{ + return -ENXIO; +} + +static inline void geni_se_tx_dma_unprep(struct device *wrapper_dev, + dma_addr_t tx_dma, int tx_len) +{ +} + +static inline void geni_se_rx_dma_unprep(struct device *wrapper_dev, + dma_addr_t rx_dma, int rx_len) +{ +} + +static inline int geni_se_qupv3_hw_version(struct device *wrapper_dev, + unsigned int *major, unsigned int *minor, unsigned int *step) +{ + return -ENXIO; +} + +static inline int geni_se_iommu_map_buf(struct device *wrapper_dev, + dma_addr_t *iova, void *buf, size_t size, enum dma_data_direction dir) +{ + return -ENXIO; +} + +static inline void *geni_se_iommu_alloc_buf(struct device *wrapper_dev, + dma_addr_t *iova, size_t size) +{ + return NULL; +} + +static inline int geni_se_iommu_unmap_buf(struct device *wrapper_dev, + dma_addr_t *iova, size_t size, enum dma_data_direction dir) +{ + return -ENXIO; + +} + +static inline int geni_se_iommu_free_buf(struct device *wrapper_dev, + dma_addr_t *iova, void *buf, size_t size) +{ + return -ENXIO; +} + +static void geni_se_dump_dbg_regs(struct se_geni_rsc *rsc, void __iomem *base, + void *ipc) +{ +} + +#endif +#endif diff --git a/include/uapi/asm-generic/ioctls.h b/include/uapi/asm-generic/ioctls.h index cdc9f4ca8c27..833f326d6cc6 100644 --- a/include/uapi/asm-generic/ioctls.h +++ b/include/uapi/asm-generic/ioctls.h @@ -81,6 +81,9 @@ #define TIOCGPTPEER _IO('T', 0x41) /* Safely open the slave */ #define TIOCGISO7816 _IOR('T', 0x42, struct serial_iso7816) #define TIOCSISO7816 _IOWR('T', 0x43, struct serial_iso7816) +#define TIOCPMGET 0x544D /* PM get */ +#define TIOCPMPUT 0x544E /* PM put */ +#define TIOCPMACT 0x544F /* PM is active */ #define FIONCLEX 0x5450 #define FIOCLEX 0x5451