From 64bf6b260e86e778c3d95290d60f5b25e641b08e Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Tue, 27 Mar 2018 08:55:15 -0700 Subject: soc: qcom: smd-rpm: Add msm8998 compatible Add the compatible for the RPM in MSM8998, so that rpm resources can be made available. Signed-off-by: Bjorn Andersson Reviewed-by: Rob Herring Signed-off-by: Andy Gross --- drivers/soc/qcom/smd-rpm.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/soc/qcom') diff --git a/drivers/soc/qcom/smd-rpm.c b/drivers/soc/qcom/smd-rpm.c index c2346752b3ea..93517ed83355 100644 --- a/drivers/soc/qcom/smd-rpm.c +++ b/drivers/soc/qcom/smd-rpm.c @@ -226,6 +226,7 @@ static const struct of_device_id qcom_smd_rpm_of_match[] = { { .compatible = "qcom,rpm-msm8916" }, { .compatible = "qcom,rpm-msm8974" }, { .compatible = "qcom,rpm-msm8996" }, + { .compatible = "qcom,rpm-msm8998" }, {} }; MODULE_DEVICE_TABLE(of, qcom_smd_rpm_of_match); -- cgit v1.2.3-55-g7522 From eddac5af06546d2e7a0730e3dc02dde3dc91098a Mon Sep 17 00:00:00 2001 From: Karthikeyan Ramasubramanian Date: Fri, 30 Mar 2018 11:08:17 -0600 Subject: soc: qcom: Add GENI based QUP Wrapper driver This driver manages the Generic Interface (GENI) firmware based Qualcomm Universal Peripheral (QUP) Wrapper. GENI based QUP is the next generation programmable module composed of multiple Serial Engines (SE) and supports a wide range of serial interfaces like UART, SPI, I2C, I3C, etc. This driver also enables managing the serial interface independent aspects of Serial Engines. Signed-off-by: Karthikeyan Ramasubramanian Signed-off-by: Sagar Dharia Signed-off-by: Girish Mahadevan Reviewed-by: Evan Green Signed-off-by: Andy Gross --- drivers/soc/qcom/Kconfig | 9 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/qcom-geni-se.c | 748 ++++++++++++++++++++++++++++++++++++++++ include/linux/qcom-geni-se.h | 425 +++++++++++++++++++++++ 4 files changed, 1183 insertions(+) create mode 100644 drivers/soc/qcom/qcom-geni-se.c create mode 100644 include/linux/qcom-geni-se.h (limited to 'drivers/soc/qcom') diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 5c4535b545cc..d0372de29834 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -3,6 +3,15 @@ # menu "Qualcomm SoC drivers" +config QCOM_GENI_SE + tristate "QCOM GENI Serial Engine Driver" + depends on ARCH_QCOM || COMPILE_TEST + help + This driver is used to manage Generic Interface (GENI) firmware based + Qualcomm Technologies, Inc. Universal Peripheral (QUP) Wrapper. This + driver is also used to manage the common aspects of multiple Serial + Engines present in the QUP. + config QCOM_GLINK_SSR tristate "Qualcomm Glink SSR driver" depends on RPMSG diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index dcebf2814e6d..959aa748f5ac 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_QCOM_GENI_SE) += qcom-geni-se.o obj-$(CONFIG_QCOM_GLINK_SSR) += glink_ssr.o obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o obj-$(CONFIG_QCOM_MDT_LOADER) += mdt_loader.o diff --git a/drivers/soc/qcom/qcom-geni-se.c b/drivers/soc/qcom/qcom-geni-se.c new file mode 100644 index 000000000000..feed3db21c10 --- /dev/null +++ b/drivers/soc/qcom/qcom-geni-se.c @@ -0,0 +1,748 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * DOC: Overview + * + * Generic Interface (GENI) Serial Engine (SE) Wrapper driver is introduced + * to manage GENI firmware based Qualcomm Universal Peripheral (QUP) Wrapper + * controller. QUP Wrapper is designed to support various serial bus protocols + * like UART, SPI, I2C, I3C, etc. + */ + +/** + * DOC: Hardware description + * + * GENI based QUP is a highly-flexible and programmable module for supporting + * a wide range of serial interfaces like UART, SPI, I2C, I3C, etc. A single + * QUP module can provide upto 8 serial interfaces, using its internal + * serial engines. The actual configuration is determined by the target + * platform configuration. The protocol supported by each interface is + * determined by the firmware loaded to the serial engine. Each SE consists + * of a DMA Engine and GENI sub modules which enable serial engines to + * support FIFO and DMA modes of operation. + * + * + * +-----------------------------------------+ + * |QUP Wrapper | + * | +----------------------------+ | + * --QUP & SE Clocks--> | Serial Engine N | +-IO------> + * | | ... | | Interface + * <---Clock Perf.----+ +----+-----------------------+ | | + * State Interface | | Serial Engine 1 | | | + * | | | | | + * | | | | | + * <--------AHB-------> | | | | + * | | +----+ | + * | | | | + * | | | | + * <------SE IRQ------+ +----------------------------+ | + * | | + * +-----------------------------------------+ + * + * Figure 1: GENI based QUP Wrapper + * + * The GENI submodules include primary and secondary sequencers which are + * used to drive TX & RX operations. On serial interfaces that operate using + * master-slave model, primary sequencer drives both TX & RX operations. On + * serial interfaces that operate using peer-to-peer model, primary sequencer + * drives TX operation and secondary sequencer drives RX operation. + */ + +/** + * DOC: Software description + * + * GENI SE Wrapper driver is structured into 2 parts: + * + * geni_wrapper represents QUP Wrapper controller. This part of the driver + * manages QUP Wrapper information such as hardware version, clock + * performance table that is common to all the internal serial engines. + * + * geni_se represents serial engine. This part of the driver manages serial + * engine information such as clocks, containing QUP Wrapper, etc. This part + * of driver also supports operations (eg. initialize the concerned serial + * engine, select between FIFO and DMA mode of operation etc.) that are + * common to all the serial engines and are independent of serial interfaces. + */ + +#define MAX_CLK_PERF_LEVEL 32 +#define NUM_AHB_CLKS 2 + +/** + * @struct geni_wrapper - Data structure to represent the QUP Wrapper Core + * @dev: Device pointer of the QUP wrapper core + * @base: Base address of this instance of QUP wrapper core + * @ahb_clks: Handle to the primary & secondary AHB clocks + */ +struct geni_wrapper { + struct device *dev; + void __iomem *base; + struct clk_bulk_data ahb_clks[NUM_AHB_CLKS]; +}; + +#define QUP_HW_VER_REG 0x4 + +/* Common SE registers */ +#define GENI_INIT_CFG_REVISION 0x0 +#define GENI_S_INIT_CFG_REVISION 0x4 +#define GENI_OUTPUT_CTRL 0x24 +#define GENI_CGC_CTRL 0x28 +#define GENI_CLK_CTRL_RO 0x60 +#define GENI_IF_DISABLE_RO 0x64 +#define GENI_FW_S_REVISION_RO 0x6c +#define SE_GENI_BYTE_GRAN 0x254 +#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_GP_LENGTH 0x910 +#define SE_GENI_S_GP_LENGTH 0x914 +#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_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_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_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_MAX_BURST 0xd5c +#define SE_DMA_RX_FLUSH 0xd60 +#define SE_GSI_EVENT_EN 0xe18 +#define SE_IRQ_EN 0xe1c +#define SE_DMA_GENERAL_CFG 0xe30 + +/* GENI_OUTPUT_CTRL fields */ +#define DEFAULT_IO_OUTPUT_CTRL_MSK GENMASK(6, 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) + +/* 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) + +/* 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_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 + +/** + * geni_se_get_qup_hw_version() - Read the QUP wrapper Hardware version + * @se: Pointer to the corresponding serial engine. + * + * Return: Hardware Version of the wrapper. + */ +u32 geni_se_get_qup_hw_version(struct geni_se *se) +{ + struct geni_wrapper *wrapper = se->wrapper; + + return readl_relaxed(wrapper->base + QUP_HW_VER_REG); +} +EXPORT_SYMBOL(geni_se_get_qup_hw_version); + +static void geni_se_io_set_mode(void __iomem *base) +{ + u32 val; + + val = readl_relaxed(base + SE_IRQ_EN); + val |= GENI_M_IRQ_EN | GENI_S_IRQ_EN; + val |= DMA_TX_IRQ_EN | DMA_RX_IRQ_EN; + writel_relaxed(val, base + SE_IRQ_EN); + + val = readl_relaxed(base + SE_GENI_DMA_MODE_EN); + val &= ~GENI_DMA_MODE_EN; + writel_relaxed(val, base + SE_GENI_DMA_MODE_EN); + + writel_relaxed(0, base + SE_GSI_EVENT_EN); +} + +static void geni_se_io_init(void __iomem *base) +{ + u32 val; + + val = readl_relaxed(base + GENI_CGC_CTRL); + val |= DEFAULT_CGC_EN; + writel_relaxed(val, base + GENI_CGC_CTRL); + + val = readl_relaxed(base + SE_DMA_GENERAL_CFG); + val |= AHB_SEC_SLV_CLK_CGC_ON | DMA_AHB_SLV_CFG_ON; + val |= DMA_TX_CLK_CGC_ON | DMA_RX_CLK_CGC_ON; + writel_relaxed(val, base + SE_DMA_GENERAL_CFG); + + writel_relaxed(DEFAULT_IO_OUTPUT_CTRL_MSK, base + GENI_OUTPUT_CTRL); + writel_relaxed(FORCE_DEFAULT, base + GENI_FORCE_DEFAULT_REG); +} + +/** + * geni_se_init() - Initialize the GENI serial engine + * @se: Pointer to the concerned serial engine. + * @rx_wm: Receive watermark, in units of FIFO words. + * @rx_rfr_wm: Ready-for-receive watermark, in units of FIFO words. + * + * This function is used to initialize the GENI serial engine, configure + * receive watermark and ready-for-receive watermarks. + */ +void geni_se_init(struct geni_se *se, u32 rx_wm, u32 rx_rfr) +{ + u32 val; + + geni_se_io_init(se->base); + geni_se_io_set_mode(se->base); + + writel_relaxed(rx_wm, se->base + SE_GENI_RX_WATERMARK_REG); + writel_relaxed(rx_rfr, se->base + SE_GENI_RX_RFR_WATERMARK_REG); + + val = readl_relaxed(se->base + SE_GENI_M_IRQ_EN); + val |= M_COMMON_GENI_M_IRQ_EN; + writel_relaxed(val, se->base + SE_GENI_M_IRQ_EN); + + val = readl_relaxed(se->base + SE_GENI_S_IRQ_EN); + val |= S_COMMON_GENI_S_IRQ_EN; + writel_relaxed(val, se->base + SE_GENI_S_IRQ_EN); +} +EXPORT_SYMBOL(geni_se_init); + +static void geni_se_select_fifo_mode(struct geni_se *se) +{ + u32 proto = geni_se_read_proto(se); + u32 val; + + writel_relaxed(0, se->base + SE_GSI_EVENT_EN); + writel_relaxed(0xffffffff, se->base + SE_GENI_M_IRQ_CLEAR); + writel_relaxed(0xffffffff, se->base + SE_GENI_S_IRQ_CLEAR); + writel_relaxed(0xffffffff, se->base + SE_DMA_TX_IRQ_CLR); + writel_relaxed(0xffffffff, se->base + SE_DMA_RX_IRQ_CLR); + writel_relaxed(0xffffffff, se->base + SE_IRQ_EN); + + val = readl_relaxed(se->base + SE_GENI_M_IRQ_EN); + if (proto != GENI_SE_UART) { + val |= M_CMD_DONE_EN | M_TX_FIFO_WATERMARK_EN; + val |= M_RX_FIFO_WATERMARK_EN | M_RX_FIFO_LAST_EN; + } + writel_relaxed(val, se->base + SE_GENI_M_IRQ_EN); + + val = readl_relaxed(se->base + SE_GENI_S_IRQ_EN); + if (proto != GENI_SE_UART) + val |= S_CMD_DONE_EN; + writel_relaxed(val, se->base + SE_GENI_S_IRQ_EN); + + val = readl_relaxed(se->base + SE_GENI_DMA_MODE_EN); + val &= ~GENI_DMA_MODE_EN; + writel_relaxed(val, se->base + SE_GENI_DMA_MODE_EN); +} + +static void geni_se_select_dma_mode(struct geni_se *se) +{ + u32 val; + + writel_relaxed(0, se->base + SE_GSI_EVENT_EN); + writel_relaxed(0xffffffff, se->base + SE_GENI_M_IRQ_CLEAR); + writel_relaxed(0xffffffff, se->base + SE_GENI_S_IRQ_CLEAR); + writel_relaxed(0xffffffff, se->base + SE_DMA_TX_IRQ_CLR); + writel_relaxed(0xffffffff, se->base + SE_DMA_RX_IRQ_CLR); + writel_relaxed(0xffffffff, se->base + SE_IRQ_EN); + + val = readl_relaxed(se->base + SE_GENI_DMA_MODE_EN); + val |= GENI_DMA_MODE_EN; + writel_relaxed(val, se->base + SE_GENI_DMA_MODE_EN); +} + +/** + * geni_se_select_mode() - Select the serial engine transfer mode + * @se: Pointer to the concerned serial engine. + * @mode: Transfer mode to be selected. + */ +void geni_se_select_mode(struct geni_se *se, enum geni_se_xfer_mode mode) +{ + WARN_ON(mode != GENI_SE_FIFO && mode != GENI_SE_DMA); + + switch (mode) { + case GENI_SE_FIFO: + geni_se_select_fifo_mode(se); + break; + case GENI_SE_DMA: + geni_se_select_dma_mode(se); + break; + case GENI_SE_INVALID: + default: + break; + } +} +EXPORT_SYMBOL(geni_se_select_mode); + +/** + * DOC: Overview + * + * GENI FIFO packing is highly configurable. TX/RX packing/unpacking consist + * of up to 4 operations, each operation represented by 4 configuration vectors + * of 10 bits programmed in GENI_TX_PACKING_CFG0 and GENI_TX_PACKING_CFG1 for + * TX FIFO and in GENI_RX_PACKING_CFG0 and GENI_RX_PACKING_CFG1 for RX FIFO. + * Refer to below examples for detailed bit-field description. + * + * Example 1: word_size = 7, packing_mode = 4 x 8, msb_to_lsb = 1 + * + * +-----------+-------+-------+-------+-------+ + * | | vec_0 | vec_1 | vec_2 | vec_3 | + * +-----------+-------+-------+-------+-------+ + * | start | 0x6 | 0xe | 0x16 | 0x1e | + * | direction | 1 | 1 | 1 | 1 | + * | length | 6 | 6 | 6 | 6 | + * | stop | 0 | 0 | 0 | 1 | + * +-----------+-------+-------+-------+-------+ + * + * Example 2: word_size = 15, packing_mode = 2 x 16, msb_to_lsb = 0 + * + * +-----------+-------+-------+-------+-------+ + * | | vec_0 | vec_1 | vec_2 | vec_3 | + * +-----------+-------+-------+-------+-------+ + * | start | 0x0 | 0x8 | 0x10 | 0x18 | + * | direction | 0 | 0 | 0 | 0 | + * | length | 7 | 6 | 7 | 6 | + * | stop | 0 | 0 | 0 | 1 | + * +-----------+-------+-------+-------+-------+ + * + * Example 3: word_size = 23, packing_mode = 1 x 32, msb_to_lsb = 1 + * + * +-----------+-------+-------+-------+-------+ + * | | vec_0 | vec_1 | vec_2 | vec_3 | + * +-----------+-------+-------+-------+-------+ + * | start | 0x16 | 0xe | 0x6 | 0x0 | + * | direction | 1 | 1 | 1 | 1 | + * | length | 7 | 7 | 6 | 0 | + * | stop | 0 | 0 | 1 | 0 | + * +-----------+-------+-------+-------+-------+ + * + */ + +#define NUM_PACKING_VECTORS 4 +#define PACKING_START_SHIFT 5 +#define PACKING_DIR_SHIFT 4 +#define PACKING_LEN_SHIFT 1 +#define PACKING_STOP_BIT BIT(0) +#define PACKING_VECTOR_SHIFT 10 +/** + * geni_se_config_packing() - Packing configuration of the serial engine + * @se: Pointer to the concerned serial engine + * @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. + * @tx_cfg: Flag to configure the TX Packing. + * @rx_cfg: Flag to configure the RX Packing. + * + * This function is used to configure the packing rules for the current + * transfer. + */ +void geni_se_config_packing(struct geni_se *se, int bpw, int pack_words, + bool msb_to_lsb, bool tx_cfg, bool rx_cfg) +{ + u32 cfg0, cfg1, cfg[NUM_PACKING_VECTORS] = {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 = ALIGN(bpw, BITS_PER_BYTE); + int iter = (ceil_bpw * pack_words) / BITS_PER_BYTE; + int i; + + if (iter <= 0 || iter > NUM_PACKING_VECTORS) + return; + + for (i = 0; i < iter; i++) { + len = min_t(int, temp_bpw, BITS_PER_BYTE) - 1; + cfg[i] = idx << PACKING_START_SHIFT; + cfg[i] |= msb_to_lsb << PACKING_DIR_SHIFT; + cfg[i] |= len << PACKING_LEN_SHIFT; + + if (temp_bpw <= BITS_PER_BYTE) { + idx = ((i + 1) * BITS_PER_BYTE) + idx_start; + temp_bpw = bpw; + } else { + idx = idx + idx_delta; + temp_bpw = temp_bpw - BITS_PER_BYTE; + } + } + cfg[iter - 1] |= PACKING_STOP_BIT; + cfg0 = cfg[0] | (cfg[1] << PACKING_VECTOR_SHIFT); + cfg1 = cfg[2] | (cfg[3] << PACKING_VECTOR_SHIFT); + + if (tx_cfg) { + writel_relaxed(cfg0, se->base + SE_GENI_TX_PACKING_CFG0); + writel_relaxed(cfg1, se->base + SE_GENI_TX_PACKING_CFG1); + } + if (rx_cfg) { + writel_relaxed(cfg0, se->base + SE_GENI_RX_PACKING_CFG0); + writel_relaxed(cfg1, se->base + SE_GENI_RX_PACKING_CFG1); + } + + /* + * Number of protocol words in each FIFO entry + * 0 - 4x8, four words in each entry, max word size of 8 bits + * 1 - 2x16, two words in each entry, max word size of 16 bits + * 2 - 1x32, one word in each entry, max word size of 32 bits + * 3 - undefined + */ + if (pack_words || bpw == 32) + writel_relaxed(bpw / 16, se->base + SE_GENI_BYTE_GRAN); +} +EXPORT_SYMBOL(geni_se_config_packing); + +static void geni_se_clks_off(struct geni_se *se) +{ + struct geni_wrapper *wrapper = se->wrapper; + + clk_disable_unprepare(se->clk); + clk_bulk_disable_unprepare(ARRAY_SIZE(wrapper->ahb_clks), + wrapper->ahb_clks); +} + +/** + * geni_se_resources_off() - Turn off resources associated with the serial + * engine + * @se: Pointer to the concerned serial engine. + * + * Return: 0 on success, standard Linux error codes on failure/error. + */ +int geni_se_resources_off(struct geni_se *se) +{ + int ret; + + ret = pinctrl_pm_select_sleep_state(se->dev); + if (ret) + return ret; + + geni_se_clks_off(se); + return 0; +} +EXPORT_SYMBOL(geni_se_resources_off); + +static int geni_se_clks_on(struct geni_se *se) +{ + int ret; + struct geni_wrapper *wrapper = se->wrapper; + + ret = clk_bulk_prepare_enable(ARRAY_SIZE(wrapper->ahb_clks), + wrapper->ahb_clks); + if (ret) + return ret; + + ret = clk_prepare_enable(se->clk); + if (ret) + clk_bulk_disable_unprepare(ARRAY_SIZE(wrapper->ahb_clks), + wrapper->ahb_clks); + return ret; +} + +/** + * geni_se_resources_on() - Turn on resources associated with the serial + * engine + * @se: Pointer to the concerned serial engine. + * + * Return: 0 on success, standard Linux error codes on failure/error. + */ +int geni_se_resources_on(struct geni_se *se) +{ + int ret; + + ret = geni_se_clks_on(se); + if (ret) + return ret; + + ret = pinctrl_pm_select_default_state(se->dev); + if (ret) + geni_se_clks_off(se); + + return ret; +} +EXPORT_SYMBOL(geni_se_resources_on); + +/** + * geni_se_clk_tbl_get() - Get the clock table to program DFS + * @se: Pointer to the concerned serial engine. + * @tbl: Table in which the output is returned. + * + * This function is called by the protocol drivers to determine the different + * clock frequencies supported by serial 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 geni_se *se, unsigned long **tbl) +{ + unsigned long freq = 0; + int i; + + if (se->clk_perf_tbl) { + *tbl = se->clk_perf_tbl; + return se->num_clk_levels; + } + + se->clk_perf_tbl = devm_kcalloc(se->dev, MAX_CLK_PERF_LEVEL, + sizeof(*se->clk_perf_tbl), + GFP_KERNEL); + if (!se->clk_perf_tbl) + return -ENOMEM; + + for (i = 0; i < MAX_CLK_PERF_LEVEL; i++) { + freq = clk_round_rate(se->clk, freq + 1); + if (!freq || freq == se->clk_perf_tbl[i - 1]) + break; + se->clk_perf_tbl[i] = freq; + } + se->num_clk_levels = i; + *tbl = se->clk_perf_tbl; + return se->num_clk_levels; +} +EXPORT_SYMBOL(geni_se_clk_tbl_get); + +/** + * geni_se_clk_freq_match() - Get the matching or closest SE clock frequency + * @se: Pointer to the concerned serial engine. + * @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 exact multiple of the requested frequency, as provided by the serial + * engine clock in order to meet the performance requirements. If there is + * no matching or exact multiple of the requested frequency found, then it + * selects the closest floor frequency, if exact flag is not set. + * + * Return: 0 on success, standard Linux error codes on failure. + */ +int geni_se_clk_freq_match(struct geni_se *se, unsigned long req_freq, + unsigned int *index, unsigned long *res_freq, + bool exact) +{ + unsigned long *tbl; + int num_clk_levels; + int i; + + num_clk_levels = geni_se_clk_tbl_get(se, &tbl); + if (num_clk_levels < 0) + return num_clk_levels; + + if (num_clk_levels == 0) + return -EINVAL; + + *res_freq = 0; + for (i = 0; i < num_clk_levels; i++) { + if (!(tbl[i] % req_freq)) { + *index = i; + *res_freq = tbl[i]; + return 0; + } + + if (!(*res_freq) || ((tbl[i] > *res_freq) && + (tbl[i] < req_freq))) { + *index = i; + *res_freq = tbl[i]; + } + } + + if (exact) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL(geni_se_clk_freq_match); + +#define GENI_SE_DMA_DONE_EN BIT(0) +#define GENI_SE_DMA_EOT_EN BIT(1) +#define GENI_SE_DMA_AHB_ERR_EN BIT(2) +#define GENI_SE_DMA_EOT_BUF BIT(0) +/** + * geni_se_tx_dma_prep() - Prepare the serial engine for TX DMA transfer + * @se: Pointer to the concerned serial engine. + * @buf: Pointer to the TX buffer. + * @len: Length of the TX buffer. + * @iova: 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 failure. + */ +int geni_se_tx_dma_prep(struct geni_se *se, void *buf, size_t len, + dma_addr_t *iova) +{ + struct geni_wrapper *wrapper = se->wrapper; + u32 val; + + *iova = dma_map_single(wrapper->dev, buf, len, DMA_TO_DEVICE); + if (dma_mapping_error(wrapper->dev, *iova)) + return -EIO; + + val = GENI_SE_DMA_DONE_EN; + val |= GENI_SE_DMA_EOT_EN; + val |= GENI_SE_DMA_AHB_ERR_EN; + writel_relaxed(val, se->base + SE_DMA_TX_IRQ_EN_SET); + writel_relaxed(lower_32_bits(*iova), se->base + SE_DMA_TX_PTR_L); + writel_relaxed(upper_32_bits(*iova), se->base + SE_DMA_TX_PTR_H); + writel_relaxed(GENI_SE_DMA_EOT_BUF, se->base + SE_DMA_TX_ATTR); + writel_relaxed(len, se->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 + * @se: Pointer to the concerned serial engine. + * @buf: Pointer to the RX buffer. + * @len: Length of the RX buffer. + * @iova: 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 failure. + */ +int geni_se_rx_dma_prep(struct geni_se *se, void *buf, size_t len, + dma_addr_t *iova) +{ + struct geni_wrapper *wrapper = se->wrapper; + u32 val; + + *iova = dma_map_single(wrapper->dev, buf, len, DMA_FROM_DEVICE); + if (dma_mapping_error(wrapper->dev, *iova)) + return -EIO; + + val = GENI_SE_DMA_DONE_EN; + val |= GENI_SE_DMA_EOT_EN; + val |= GENI_SE_DMA_AHB_ERR_EN; + writel_relaxed(val, se->base + SE_DMA_RX_IRQ_EN_SET); + writel_relaxed(lower_32_bits(*iova), se->base + SE_DMA_RX_PTR_L); + writel_relaxed(upper_32_bits(*iova), se->base + SE_DMA_RX_PTR_H); + /* RX does not have EOT buffer type bit. So just reset RX_ATTR */ + writel_relaxed(0, se->base + SE_DMA_RX_ATTR); + writel_relaxed(len, se->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 + * @se: Pointer to the concerned serial engine. + * @iova: DMA address of the TX buffer. + * @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 geni_se *se, dma_addr_t iova, size_t len) +{ + struct geni_wrapper *wrapper = se->wrapper; + + if (iova && !dma_mapping_error(wrapper->dev, iova)) + dma_unmap_single(wrapper->dev, iova, len, DMA_TO_DEVICE); +} +EXPORT_SYMBOL(geni_se_tx_dma_unprep); + +/** + * geni_se_rx_dma_unprep() - Unprepare the serial engine after RX DMA transfer + * @se: Pointer to the concerned serial engine. + * @iova: DMA address of the RX buffer. + * @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 geni_se *se, dma_addr_t iova, size_t len) +{ + struct geni_wrapper *wrapper = se->wrapper; + + if (iova && !dma_mapping_error(wrapper->dev, iova)) + dma_unmap_single(wrapper->dev, iova, len, DMA_FROM_DEVICE); +} +EXPORT_SYMBOL(geni_se_rx_dma_unprep); + +static int geni_se_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct geni_wrapper *wrapper; + int ret; + + wrapper = devm_kzalloc(dev, sizeof(*wrapper), GFP_KERNEL); + if (!wrapper) + return -ENOMEM; + + wrapper->dev = dev; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + wrapper->base = devm_ioremap_resource(dev, res); + if (IS_ERR(wrapper->base)) + return PTR_ERR(wrapper->base); + + wrapper->ahb_clks[0].id = "m-ahb"; + wrapper->ahb_clks[1].id = "s-ahb"; + ret = devm_clk_bulk_get(dev, NUM_AHB_CLKS, wrapper->ahb_clks); + if (ret) { + dev_err(dev, "Err getting AHB clks %d\n", ret); + return ret; + } + + dev_set_drvdata(dev, wrapper); + dev_dbg(dev, "GENI SE Driver probed\n"); + return devm_of_platform_populate(dev); +} + +static const struct of_device_id geni_se_dt_match[] = { + { .compatible = "qcom,geni-se-qup", }, + {} +}; +MODULE_DEVICE_TABLE(of, geni_se_dt_match); + +static struct platform_driver geni_se_driver = { + .driver = { + .name = "geni_se_qup", + .of_match_table = geni_se_dt_match, + }, + .probe = geni_se_probe, +}; +module_platform_driver(geni_se_driver); + +MODULE_DESCRIPTION("GENI Serial Engine Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/qcom-geni-se.h b/include/linux/qcom-geni-se.h new file mode 100644 index 000000000000..5d6144977828 --- /dev/null +++ b/include/linux/qcom-geni-se.h @@ -0,0 +1,425 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef _LINUX_QCOM_GENI_SE +#define _LINUX_QCOM_GENI_SE + +/* Transfer mode supported by GENI Serial Engines */ +enum geni_se_xfer_mode { + GENI_SE_INVALID, + GENI_SE_FIFO, + GENI_SE_DMA, +}; + +/* Protocols supported by GENI Serial Engines */ +enum geni_se_protocol_type { + GENI_SE_NONE, + GENI_SE_SPI, + GENI_SE_UART, + GENI_SE_I2C, + GENI_SE_I3C, +}; + +struct geni_wrapper; +struct clk; + +/** + * struct geni_se - GENI Serial Engine + * @base: Base Address of the Serial Engine's register block + * @dev: Pointer to the Serial Engine device + * @wrapper: Pointer to the parent QUP Wrapper core + * @clk: Handle to the core serial engine clock + * @num_clk_levels: Number of valid clock levels in clk_perf_tbl + * @clk_perf_tbl: Table of clock frequency input to serial engine clock + */ +struct geni_se { + void __iomem *base; + struct device *dev; + struct geni_wrapper *wrapper; + struct clk *clk; + unsigned int num_clk_levels; + unsigned long *clk_perf_tbl; +}; + +/* Common SE registers */ +#define GENI_FORCE_DEFAULT_REG 0x20 +#define SE_GENI_STATUS 0x40 +#define GENI_SER_M_CLK_CFG 0x48 +#define GENI_SER_S_CLK_CFG 0x4c +#define GENI_FW_REVISION_RO 0x68 +#define SE_GENI_CLK_SEL 0x7c +#define SE_GENI_DMA_MODE_EN 0x258 +#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_DMA_TX_IRQ_STAT 0xc40 +#define SE_DMA_TX_IRQ_CLR 0xc44 +#define SE_DMA_TX_FSM_RST 0xc58 +#define SE_DMA_RX_IRQ_STAT 0xd40 +#define SE_DMA_RX_IRQ_CLR 0xd44 +#define SE_DMA_RX_FSM_RST 0xd58 +#define SE_HW_PARAM_0 0xe24 +#define SE_HW_PARAM_1 0xe28 + +/* GENI_FORCE_DEFAULT_REG fields */ +#define FORCE_DEFAULT BIT(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 + +/* GENI_FW_REVISION_RO fields */ +#define FW_REV_PROTOCOL_MSK GENMASK(15, 8) +#define FW_REV_PROTOCOL_SHFT 8 + +/* 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_GENI_IOS fields */ +#define IO2_DATA_IN BIT(1) +#define RX_DATA_IN BIT(0) + +/* 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) + +/* 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 + +#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) + +#if IS_ENABLED(CONFIG_QCOM_GENI_SE) + +u32 geni_se_get_qup_hw_version(struct geni_se *se); + +#define geni_se_get_wrapper_version(se, major, minor, step) do { \ + u32 ver; \ +\ + ver = geni_se_get_qup_hw_version(se); \ + major = (ver & HW_VER_MAJOR_MASK) >> HW_VER_MAJOR_SHFT; \ + minor = (ver & HW_VER_MINOR_MASK) >> HW_VER_MINOR_SHFT; \ + step = version & HW_VER_STEP_MASK; \ +} while (0) + +/** + * geni_se_read_proto() - Read the protocol configured for a serial engine + * @se: Pointer to the concerned serial engine. + * + * Return: Protocol value as configured in the serial engine. + */ +static inline u32 geni_se_read_proto(struct geni_se *se) +{ + u32 val; + + val = readl_relaxed(se->base + GENI_FW_REVISION_RO); + + return (val & FW_REV_PROTOCOL_MSK) >> FW_REV_PROTOCOL_SHFT; +} + +/** + * geni_se_setup_m_cmd() - Setup the primary sequencer + * @se: Pointer to the concerned serial engine. + * @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 associated parameters. + */ +static inline void geni_se_setup_m_cmd(struct geni_se *se, u32 cmd, u32 params) +{ + u32 m_cmd; + + m_cmd = (cmd << M_OPCODE_SHFT) | (params & M_PARAMS_MSK); + writel_relaxed(m_cmd, se->base + SE_GENI_M_CMD0); +} + +/** + * geni_se_setup_s_cmd() - Setup the secondary sequencer + * @se: Pointer to the concerned serial engine. + * @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 associated parameters. + */ +static inline void geni_se_setup_s_cmd(struct geni_se *se, u32 cmd, u32 params) +{ + u32 s_cmd; + + s_cmd = readl_relaxed(se->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); + writel_relaxed(s_cmd, se->base + SE_GENI_S_CMD0); +} + +/** + * geni_se_cancel_m_cmd() - Cancel the command configured in the primary + * sequencer + * @se: Pointer to the concerned serial engine. + * + * This function is used to cancel the currently configured command in the + * primary sequencer. + */ +static inline void geni_se_cancel_m_cmd(struct geni_se *se) +{ + writel_relaxed(M_GENI_CMD_CANCEL, se->base + SE_GENI_M_CMD_CTRL_REG); +} + +/** + * geni_se_cancel_s_cmd() - Cancel the command configured in the secondary + * sequencer + * @se: Pointer to the concerned serial engine. + * + * This function is used to cancel the currently configured command in the + * secondary sequencer. + */ +static inline void geni_se_cancel_s_cmd(struct geni_se *se) +{ + writel_relaxed(S_GENI_CMD_CANCEL, se->base + SE_GENI_S_CMD_CTRL_REG); +} + +/** + * geni_se_abort_m_cmd() - Abort the command configured in the primary sequencer + * @se: Pointer to the concerned serial engine. + * + * This function is used to force abort the currently configured command in the + * primary sequencer. + */ +static inline void geni_se_abort_m_cmd(struct geni_se *se) +{ + writel_relaxed(M_GENI_CMD_ABORT, se->base + SE_GENI_M_CMD_CTRL_REG); +} + +/** + * geni_se_abort_s_cmd() - Abort the command configured in the secondary + * sequencer + * @se: Pointer to the concerned serial engine. + * + * This function is used to force abort the currently configured command in the + * secondary sequencer. + */ +static inline void geni_se_abort_s_cmd(struct geni_se *se) +{ + writel_relaxed(S_GENI_CMD_ABORT, se->base + SE_GENI_S_CMD_CTRL_REG); +} + +/** + * geni_se_get_tx_fifo_depth() - Get the TX fifo depth of the serial engine + * @se: Pointer to the concerned serial engine. + * + * 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. + */ +static inline u32 geni_se_get_tx_fifo_depth(struct geni_se *se) +{ + u32 val; + + val = readl_relaxed(se->base + SE_HW_PARAM_0); + + return (val & TX_FIFO_DEPTH_MSK) >> TX_FIFO_DEPTH_SHFT; +} + +/** + * geni_se_get_tx_fifo_width() - Get the TX fifo width of the serial engine + * @se: Pointer to the concerned serial engine. + * + * 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 + */ +static inline u32 geni_se_get_tx_fifo_width(struct geni_se *se) +{ + u32 val; + + val = readl_relaxed(se->base + SE_HW_PARAM_0); + + return (val & TX_FIFO_WIDTH_MSK) >> TX_FIFO_WIDTH_SHFT; +} + +/** + * geni_se_get_rx_fifo_depth() - Get the RX fifo depth of the serial engine + * @se: Pointer to the concerned serial engine. + * + * 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 + */ +static inline u32 geni_se_get_rx_fifo_depth(struct geni_se *se) +{ + u32 val; + + val = readl_relaxed(se->base + SE_HW_PARAM_1); + + return (val & RX_FIFO_DEPTH_MSK) >> RX_FIFO_DEPTH_SHFT; +} + +void geni_se_init(struct geni_se *se, u32 rx_wm, u32 rx_rfr); + +void geni_se_select_mode(struct geni_se *se, enum geni_se_xfer_mode mode); + +void geni_se_config_packing(struct geni_se *se, int bpw, int pack_words, + bool msb_to_lsb, bool tx_cfg, bool rx_cfg); + +int geni_se_resources_off(struct geni_se *se); + +int geni_se_resources_on(struct geni_se *se); + +int geni_se_clk_tbl_get(struct geni_se *se, unsigned long **tbl); + +int geni_se_clk_freq_match(struct geni_se *se, unsigned long req_freq, + unsigned int *index, unsigned long *res_freq, + bool exact); + +int geni_se_tx_dma_prep(struct geni_se *se, void *buf, size_t len, + dma_addr_t *iova); + +int geni_se_rx_dma_prep(struct geni_se *se, void *buf, size_t len, + dma_addr_t *iova); + +void geni_se_tx_dma_unprep(struct geni_se *se, dma_addr_t iova, size_t len); + +void geni_se_rx_dma_unprep(struct geni_se *se, dma_addr_t iova, size_t len); +#endif +#endif -- cgit v1.2.3-55-g7522 From 312416d9171a1460b7ed8d182b5b540c910ce80d Mon Sep 17 00:00:00 2001 From: Mahesh Sivasubramanian Date: Tue, 10 Apr 2018 11:57:23 -0600 Subject: drivers: qcom: add command DB driver Command DB is a simple database in the shared memory of QCOM SoCs, that provides information regarding shared resources. Some shared resources in the SoC have properties that are probed dynamically at boot by the remote processor. The information pertaining to the SoC and the platform are made available in the shared memory. Drivers can query this information using predefined strings. Signed-off-by: Mahesh Sivasubramanian Signed-off-by: Lina Iyer Reviewed-by: Bjorn Andersson Reviewed-by: Stephen Boyd Signed-off-by: Andy Gross --- drivers/of/platform.c | 1 + drivers/soc/qcom/Kconfig | 9 ++ drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/cmd-db.c | 310 ++++++++++++++++++++++++++++++++++++++++++++++ include/soc/qcom/cmd-db.h | 45 +++++++ 5 files changed, 366 insertions(+) create mode 100644 drivers/soc/qcom/cmd-db.c create mode 100644 include/soc/qcom/cmd-db.h (limited to 'drivers/soc/qcom') diff --git a/drivers/of/platform.c b/drivers/of/platform.c index c00d81dfac0b..26fb43847f4b 100644 --- a/drivers/of/platform.c +++ b/drivers/of/platform.c @@ -494,6 +494,7 @@ EXPORT_SYMBOL_GPL(of_platform_default_populate); #ifndef CONFIG_PPC static const struct of_device_id reserved_mem_matches[] = { { .compatible = "qcom,rmtfs-mem" }, + { .compatible = "qcom,cmd-db" }, { .compatible = "ramoops" }, {} }; diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index d0372de29834..7093fe79934b 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -3,6 +3,15 @@ # menu "Qualcomm SoC drivers" +config QCOM_COMMAND_DB + bool "Qualcomm Command DB" + depends on (ARCH_QCOM && OF) || COMPILE_TEST + help + Command DB queries shared memory by key string for shared system + resources. Platform drivers that require to set state of a shared + resource on a RPM-hardened platform must use this database to get + SoC specific identifier and information for the shared resources. + config QCOM_GENI_SE tristate "QCOM GENI Serial Engine Driver" depends on ARCH_QCOM || COMPILE_TEST diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 959aa748f5ac..cbf414cfe6aa 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_QCOM_GENI_SE) += qcom-geni-se.o +obj-$(CONFIG_QCOM_COMMAND_DB) += cmd-db.o obj-$(CONFIG_QCOM_GLINK_SSR) += glink_ssr.o obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o obj-$(CONFIG_QCOM_MDT_LOADER) += mdt_loader.o diff --git a/drivers/soc/qcom/cmd-db.c b/drivers/soc/qcom/cmd-db.c new file mode 100644 index 000000000000..b5172049f608 --- /dev/null +++ b/drivers/soc/qcom/cmd-db.c @@ -0,0 +1,310 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define NUM_PRIORITY 2 +#define MAX_SLV_ID 8 +#define CMD_DB_MAGIC 0x0C0330DBUL +#define SLAVE_ID_MASK 0x7 +#define SLAVE_ID_SHIFT 16 + +#define ENTRY_HEADER(hdr) ((void *)cmd_db_header + \ + sizeof(*cmd_db_header) + \ + hdr->header_offset) + +#define RSC_OFFSET(hdr, ent) ((void *)cmd_db_header + \ + sizeof(*cmd_db_header) + \ + hdr.data_offset + ent.offset) + +/** + * struct entry_header: header for each entry in cmddb + * + * @id: resource's identifier + * @priority: unused + * @addr: the address of the resource + * @len: length of the data + * @offset: offset from :@data_offset, start of the data + */ +struct entry_header { + u64 id; + u32 priority[NUM_PRIORITY]; + u32 addr; + u16 len; + u16 offset; +}; + +/** + * struct rsc_hdr: resource header information + * + * @slv_id: id for the resource + * @header_offset: entry's header at offset from the end of the cmd_db_header + * @data_offset: entry's data at offset from the end of the cmd_db_header + * @cnt: number of entries for HW type + * @version: MSB is major, LSB is minor + * @reserved: reserved for future use. + */ +struct rsc_hdr { + u16 slv_id; + u16 header_offset; + u16 data_offset; + u16 cnt; + u16 version; + u16 reserved[3]; +}; + +/** + * struct cmd_db_header: The DB header information + * + * @version: The cmd db version + * @magic_number: constant expected in the database + * @header: array of resources + * @checksum: checksum for the header. Unused. + * @reserved: reserved memory + * @data: driver specific data + */ +struct cmd_db_header { + u32 version; + u32 magic_num; + struct rsc_hdr header[MAX_SLV_ID]; + u32 checksum; + u32 reserved; + u8 data[]; +}; + +/** + * DOC: Description of the Command DB database. + * + * At the start of the command DB memory is the cmd_db_header structure. + * The cmd_db_header holds the version, checksum, magic key as well as an + * array for header for each slave (depicted by the rsc_header). Each h/w + * based accelerator is a 'slave' (shared resource) and has slave id indicating + * the type of accelerator. The rsc_header is the header for such individual + * slaves of a given type. The entries for each of these slaves begin at the + * rsc_hdr.header_offset. In addition each slave could have auxiliary data + * that may be needed by the driver. The data for the slave starts at the + * entry_header.offset to the location pointed to by the rsc_hdr.data_offset. + * + * Drivers have a stringified key to a slave/resource. They can query the slave + * information and get the slave id and the auxiliary data and the length of the + * data. Using this information, they can format the request to be sent to the + * h/w accelerator and request a resource state. + */ + +static struct cmd_db_header *cmd_db_header; + +/** + * cmd_db_ready - Indicates if command DB is available + * + * Return: 0 on success, errno otherwise + */ +int cmd_db_ready(void) +{ + if (cmd_db_header == NULL) + return -EPROBE_DEFER; + else if (cmd_db_header->magic_num != CMD_DB_MAGIC) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL(cmd_db_ready); + +static u64 cmd_db_get_u64_id(const char *id) +{ + u64 rsc_id = 0; + u8 *ch = (u8 *)&rsc_id; + + strncpy(ch, id, min(strlen(id), sizeof(rsc_id))); + + return rsc_id; +} + +static int cmd_db_get_header(u64 query, struct entry_header *eh, + struct rsc_hdr *rh) +{ + struct rsc_hdr *rsc_hdr; + struct entry_header *ent; + int ret, i, j; + + ret = cmd_db_ready(); + if (ret) + return ret; + + if (!eh || !rh) + return -EINVAL; + + for (i = 0; i < MAX_SLV_ID; i++) { + rsc_hdr = &cmd_db_header->header[i]; + if (!rsc_hdr->slv_id) + break; + + ent = ENTRY_HEADER(rsc_hdr); + for (j = 0; j < rsc_hdr->cnt; j++, ent++) { + if (ent->id == query) + break; + } + + if (j < rsc_hdr->cnt) { + memcpy(eh, ent, sizeof(*ent)); + memcpy(rh, rsc_hdr, sizeof(*rh)); + return 0; + } + } + + return -ENODEV; +} + +static int cmd_db_get_header_by_rsc_id(const char *id, + struct entry_header *ent_hdr, + struct rsc_hdr *rsc_hdr) +{ + u64 rsc_id = cmd_db_get_u64_id(id); + + return cmd_db_get_header(rsc_id, ent_hdr, rsc_hdr); +} + +/** + * cmd_db_read_addr() - Query command db for resource id address. + * + * @id: resource id to query for address + * + * Return: resource address on success, 0 on error + * + * This is used to retrieve resource address based on resource + * id. + */ +u32 cmd_db_read_addr(const char *id) +{ + int ret; + struct entry_header ent; + struct rsc_hdr rsc_hdr; + + ret = cmd_db_get_header_by_rsc_id(id, &ent, &rsc_hdr); + + return ret < 0 ? 0 : ent.addr; +} +EXPORT_SYMBOL(cmd_db_read_addr); + +/** + * cmd_db_read_aux_data() - Query command db for aux data. + * + * @id: Resource to retrieve AUX Data on. + * @data: Data buffer to copy returned aux data to. Returns size on NULL + * @len: Caller provides size of data buffer passed in. + * + * Return: size of data on success, errno otherwise + */ +int cmd_db_read_aux_data(const char *id, u8 *data, size_t len) +{ + int ret; + struct entry_header ent; + struct rsc_hdr rsc_hdr; + + if (!data) + return -EINVAL; + + ret = cmd_db_get_header_by_rsc_id(id, &ent, &rsc_hdr); + if (ret) + return ret; + + if (len < ent.len) + return -EINVAL; + + len = min_t(u16, ent.len, len); + memcpy(data, RSC_OFFSET(rsc_hdr, ent), len); + + return len; +} +EXPORT_SYMBOL(cmd_db_read_aux_data); + +/** + * cmd_db_read_aux_data_len - Get the length of the auxiliary data stored in DB. + * + * @id: Resource to retrieve AUX Data. + * + * Return: size on success, 0 on error + */ +size_t cmd_db_read_aux_data_len(const char *id) +{ + int ret; + struct entry_header ent; + struct rsc_hdr rsc_hdr; + + ret = cmd_db_get_header_by_rsc_id(id, &ent, &rsc_hdr); + + return ret < 0 ? 0 : ent.len; +} +EXPORT_SYMBOL(cmd_db_read_aux_data_len); + +/** + * cmd_db_read_slave_id - Get the slave ID for a given resource address + * + * @id: Resource id to query the DB for version + * + * Return: cmd_db_hw_type enum on success, CMD_DB_HW_INVALID on error + */ +enum cmd_db_hw_type cmd_db_read_slave_id(const char *id) +{ + int ret; + struct entry_header ent; + struct rsc_hdr rsc_hdr; + + ret = cmd_db_get_header_by_rsc_id(id, &ent, &rsc_hdr); + + return ret < 0 ? CMD_DB_HW_INVALID : + (ent.addr >> SLAVE_ID_SHIFT) & SLAVE_ID_MASK; +} +EXPORT_SYMBOL(cmd_db_read_slave_id); + +static int cmd_db_dev_probe(struct platform_device *pdev) +{ + struct reserved_mem *rmem; + int ret = 0; + + rmem = of_reserved_mem_lookup(pdev->dev.of_node); + if (!rmem) { + dev_err(&pdev->dev, "failed to acquire memory region\n"); + return -EINVAL; + } + + cmd_db_header = memremap(rmem->base, rmem->size, MEMREMAP_WB); + if (IS_ERR_OR_NULL(cmd_db_header)) { + ret = PTR_ERR(cmd_db_header); + cmd_db_header = NULL; + return ret; + } + + if (cmd_db_header->magic_num != CMD_DB_MAGIC) { + dev_err(&pdev->dev, "Invalid Command DB Magic\n"); + return -EINVAL; + } + + return 0; +} + +static const struct of_device_id cmd_db_match_table[] = { + { .compatible = "qcom,cmd-db" }, + { }, +}; + +static struct platform_driver cmd_db_dev_driver = { + .probe = cmd_db_dev_probe, + .driver = { + .name = "cmd-db", + .of_match_table = cmd_db_match_table, + }, +}; + +static int __init cmd_db_device_init(void) +{ + return platform_driver_register(&cmd_db_dev_driver); +} +arch_initcall(cmd_db_device_init); diff --git a/include/soc/qcom/cmd-db.h b/include/soc/qcom/cmd-db.h new file mode 100644 index 000000000000..578180cbc134 --- /dev/null +++ b/include/soc/qcom/cmd-db.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. */ + +#ifndef __QCOM_COMMAND_DB_H__ +#define __QCOM_COMMAND_DB_H__ + + +enum cmd_db_hw_type { + CMD_DB_HW_INVALID = 0, + CMD_DB_HW_MIN = 3, + CMD_DB_HW_ARC = CMD_DB_HW_MIN, + CMD_DB_HW_VRM = 4, + CMD_DB_HW_BCM = 5, + CMD_DB_HW_MAX = CMD_DB_HW_BCM, + CMD_DB_HW_ALL = 0xff, +}; + +#if IS_ENABLED(CONFIG_QCOM_COMMAND_DB) +u32 cmd_db_read_addr(const char *resource_id); + +int cmd_db_read_aux_data(const char *resource_id, u8 *data, size_t len); + +size_t cmd_db_read_aux_data_len(const char *resource_id); + +enum cmd_db_hw_type cmd_db_read_slave_id(const char *resource_id); + +int cmd_db_ready(void); +#else +static inline u32 cmd_db_read_addr(const char *resource_id) +{ return 0; } + +static inline int cmd_db_read_aux_data(const char *resource_id, u8 *data, + size_t len) +{ return -ENODEV; } + +static inline size_t cmd_db_read_aux_data_len(const char *resource_id) +{ return -ENODEV; } + +static inline enum cmd_db_hw_type cmd_db_read_slave_id(const char *resource_id) +{ return -ENODEV; } + +static inline int cmd_db_ready(void) +{ return -ENODEV; } +#endif /* CONFIG_QCOM_COMMAND_DB */ +#endif /* __QCOM_COMMAND_DB_H__ */ -- cgit v1.2.3-55-g7522 From 36b0aefdee6e203f3130d7444bf5103139ecbe25 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Thu, 19 Apr 2018 11:29:24 -0700 Subject: soc: qcom: cmd-db: Make endian-agnostic This driver deals with memory that is stored in little-endian format. Update the structures with the proper little-endian types and then do the proper conversions when reading the fields. Note that we compare the ids with a memcmp() because we already pad out the string 'id' field to exactly 8 bytes with the strncpy() onto the stack. Cc: Mahesh Sivasubramanian Cc: Lina Iyer Cc: Bjorn Andersson Cc: Evan Green Signed-off-by: Stephen Boyd Signed-off-by: Andy Gross --- drivers/soc/qcom/cmd-db.c | 131 ++++++++++++++++++++++++---------------------- 1 file changed, 69 insertions(+), 62 deletions(-) (limited to 'drivers/soc/qcom') diff --git a/drivers/soc/qcom/cmd-db.c b/drivers/soc/qcom/cmd-db.c index b5172049f608..a6f646295f06 100644 --- a/drivers/soc/qcom/cmd-db.c +++ b/drivers/soc/qcom/cmd-db.c @@ -13,18 +13,9 @@ #define NUM_PRIORITY 2 #define MAX_SLV_ID 8 -#define CMD_DB_MAGIC 0x0C0330DBUL #define SLAVE_ID_MASK 0x7 #define SLAVE_ID_SHIFT 16 -#define ENTRY_HEADER(hdr) ((void *)cmd_db_header + \ - sizeof(*cmd_db_header) + \ - hdr->header_offset) - -#define RSC_OFFSET(hdr, ent) ((void *)cmd_db_header + \ - sizeof(*cmd_db_header) + \ - hdr.data_offset + ent.offset) - /** * struct entry_header: header for each entry in cmddb * @@ -35,11 +26,11 @@ * @offset: offset from :@data_offset, start of the data */ struct entry_header { - u64 id; - u32 priority[NUM_PRIORITY]; - u32 addr; - u16 len; - u16 offset; + u8 id[8]; + __le32 priority[NUM_PRIORITY]; + __le32 addr; + __le16 len; + __le16 offset; }; /** @@ -53,30 +44,30 @@ struct entry_header { * @reserved: reserved for future use. */ struct rsc_hdr { - u16 slv_id; - u16 header_offset; - u16 data_offset; - u16 cnt; - u16 version; - u16 reserved[3]; + __le16 slv_id; + __le16 header_offset; + __le16 data_offset; + __le16 cnt; + __le16 version; + __le16 reserved[3]; }; /** * struct cmd_db_header: The DB header information * * @version: The cmd db version - * @magic_number: constant expected in the database + * @magic: constant expected in the database * @header: array of resources * @checksum: checksum for the header. Unused. * @reserved: reserved memory * @data: driver specific data */ struct cmd_db_header { - u32 version; - u32 magic_num; + __le32 version; + u8 magic[4]; struct rsc_hdr header[MAX_SLV_ID]; - u32 checksum; - u32 reserved; + __le32 checksum; + __le32 reserved; u8 data[]; }; @@ -99,8 +90,34 @@ struct cmd_db_header { * h/w accelerator and request a resource state. */ +static const u8 CMD_DB_MAGIC[] = { 0xdb, 0x30, 0x03, 0x0c }; + +static bool cmd_db_magic_matches(const struct cmd_db_header *header) +{ + const u8 *magic = header->magic; + + return memcmp(magic, CMD_DB_MAGIC, ARRAY_SIZE(CMD_DB_MAGIC)) == 0; +} + static struct cmd_db_header *cmd_db_header; + +static inline void *rsc_to_entry_header(struct rsc_hdr *hdr) +{ + u16 offset = le16_to_cpu(hdr->header_offset); + + return cmd_db_header->data + offset; +} + +static inline void * +rsc_offset(struct rsc_hdr *hdr, struct entry_header *ent) +{ + u16 offset = le16_to_cpu(hdr->data_offset); + u16 loffset = le16_to_cpu(ent->offset); + + return cmd_db_header->data + offset + loffset; +} + /** * cmd_db_ready - Indicates if command DB is available * @@ -110,29 +127,20 @@ int cmd_db_ready(void) { if (cmd_db_header == NULL) return -EPROBE_DEFER; - else if (cmd_db_header->magic_num != CMD_DB_MAGIC) + else if (!cmd_db_magic_matches(cmd_db_header)) return -EINVAL; return 0; } EXPORT_SYMBOL(cmd_db_ready); -static u64 cmd_db_get_u64_id(const char *id) -{ - u64 rsc_id = 0; - u8 *ch = (u8 *)&rsc_id; - - strncpy(ch, id, min(strlen(id), sizeof(rsc_id))); - - return rsc_id; -} - -static int cmd_db_get_header(u64 query, struct entry_header *eh, +static int cmd_db_get_header(const char *id, struct entry_header *eh, struct rsc_hdr *rh) { struct rsc_hdr *rsc_hdr; struct entry_header *ent; int ret, i, j; + u8 query[8]; ret = cmd_db_ready(); if (ret) @@ -141,18 +149,21 @@ static int cmd_db_get_header(u64 query, struct entry_header *eh, if (!eh || !rh) return -EINVAL; + /* Pad out query string to same length as in DB */ + strncpy(query, id, sizeof(query)); + for (i = 0; i < MAX_SLV_ID; i++) { rsc_hdr = &cmd_db_header->header[i]; if (!rsc_hdr->slv_id) break; - ent = ENTRY_HEADER(rsc_hdr); - for (j = 0; j < rsc_hdr->cnt; j++, ent++) { - if (ent->id == query) + ent = rsc_to_entry_header(rsc_hdr); + for (j = 0; j < le16_to_cpu(rsc_hdr->cnt); j++, ent++) { + if (memcmp(ent->id, query, sizeof(ent->id)) == 0) break; } - if (j < rsc_hdr->cnt) { + if (j < le16_to_cpu(rsc_hdr->cnt)) { memcpy(eh, ent, sizeof(*ent)); memcpy(rh, rsc_hdr, sizeof(*rh)); return 0; @@ -162,15 +173,6 @@ static int cmd_db_get_header(u64 query, struct entry_header *eh, return -ENODEV; } -static int cmd_db_get_header_by_rsc_id(const char *id, - struct entry_header *ent_hdr, - struct rsc_hdr *rsc_hdr) -{ - u64 rsc_id = cmd_db_get_u64_id(id); - - return cmd_db_get_header(rsc_id, ent_hdr, rsc_hdr); -} - /** * cmd_db_read_addr() - Query command db for resource id address. * @@ -187,9 +189,9 @@ u32 cmd_db_read_addr(const char *id) struct entry_header ent; struct rsc_hdr rsc_hdr; - ret = cmd_db_get_header_by_rsc_id(id, &ent, &rsc_hdr); + ret = cmd_db_get_header(id, &ent, &rsc_hdr); - return ret < 0 ? 0 : ent.addr; + return ret < 0 ? 0 : le32_to_cpu(ent.addr); } EXPORT_SYMBOL(cmd_db_read_addr); @@ -207,19 +209,21 @@ int cmd_db_read_aux_data(const char *id, u8 *data, size_t len) int ret; struct entry_header ent; struct rsc_hdr rsc_hdr; + u16 ent_len; if (!data) return -EINVAL; - ret = cmd_db_get_header_by_rsc_id(id, &ent, &rsc_hdr); + ret = cmd_db_get_header(id, &ent, &rsc_hdr); if (ret) return ret; - if (len < ent.len) + ent_len = le16_to_cpu(ent.len); + if (len < ent_len) return -EINVAL; - len = min_t(u16, ent.len, len); - memcpy(data, RSC_OFFSET(rsc_hdr, ent), len); + len = min_t(u16, ent_len, len); + memcpy(data, rsc_offset(&rsc_hdr, &ent), len); return len; } @@ -238,9 +242,9 @@ size_t cmd_db_read_aux_data_len(const char *id) struct entry_header ent; struct rsc_hdr rsc_hdr; - ret = cmd_db_get_header_by_rsc_id(id, &ent, &rsc_hdr); + ret = cmd_db_get_header(id, &ent, &rsc_hdr); - return ret < 0 ? 0 : ent.len; + return ret < 0 ? 0 : le16_to_cpu(ent.len); } EXPORT_SYMBOL(cmd_db_read_aux_data_len); @@ -256,11 +260,14 @@ enum cmd_db_hw_type cmd_db_read_slave_id(const char *id) int ret; struct entry_header ent; struct rsc_hdr rsc_hdr; + u32 addr; - ret = cmd_db_get_header_by_rsc_id(id, &ent, &rsc_hdr); + ret = cmd_db_get_header(id, &ent, &rsc_hdr); + if (ret < 0) + return CMD_DB_HW_INVALID; - return ret < 0 ? CMD_DB_HW_INVALID : - (ent.addr >> SLAVE_ID_SHIFT) & SLAVE_ID_MASK; + addr = le32_to_cpu(ent.addr); + return (addr >> SLAVE_ID_SHIFT) & SLAVE_ID_MASK; } EXPORT_SYMBOL(cmd_db_read_slave_id); @@ -282,7 +289,7 @@ static int cmd_db_dev_probe(struct platform_device *pdev) return ret; } - if (cmd_db_header->magic_num != CMD_DB_MAGIC) { + if (!cmd_db_magic_matches(cmd_db_header)) { dev_err(&pdev->dev, "Invalid Command DB Magic\n"); return -EINVAL; } -- cgit v1.2.3-55-g7522 From 7070874973eeccd867628a5c9eaefee3e832ccaa Mon Sep 17 00:00:00 2001 From: Alex Elder Date: Tue, 10 Apr 2018 17:25:37 -0500 Subject: soc: qcom: smem: fix first cache entry calculation Cached items are found at the high end of an smem partition. A cached item's shared memory precedes the private entry structure that describes it. The address of the structure describing the first cached item should be returned by phdr_to_first_cached_entry(). However the function calculates the start address using the wrong structure size. Fix this by computing the first item's entry structure address by subtracting the size of a private entry structure rather than a partition header structure. Signed-off-by: Alex Elder Reviewed-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/smem.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/soc/qcom') diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c index 0b94d62fad2b..7f38c5e11440 100644 --- a/drivers/soc/qcom/smem.c +++ b/drivers/soc/qcom/smem.c @@ -292,8 +292,9 @@ static void *phdr_to_first_cached_entry(struct smem_partition_header *phdr, size_t cacheline) { void *p = phdr; + struct smem_private_entry *e; - return p + le32_to_cpu(phdr->size) - ALIGN(sizeof(*phdr), cacheline); + return p + le32_to_cpu(phdr->size) - ALIGN(sizeof(*e), cacheline); } static void *phdr_to_last_cached_entry(struct smem_partition_header *phdr) -- cgit v1.2.3-55-g7522 From e221a1da97d97a9b452f8be78bd14333bb5605aa Mon Sep 17 00:00:00 2001 From: Alex Elder Date: Tue, 10 Apr 2018 17:25:38 -0500 Subject: soc: qcom: smem: return proper type for cached entry functions What phdr_to_last_uncached_entry() returns is the address of the start of the free space following all allocated uncached entries. It really doesn't refer to an actual (initialized) private entry structure. Similarly phdr_to_last_cached_entry() returns the address of the end of free space, preceding the last allocated cache entry. Change both functions' return type to be pointer to void to reflect this. Meanwhile, phdr_to_first_cached_entry() really *does* point to a private entry structure, so change its return type to reflect this fact. Signed-off-by: Alex Elder Reviewed-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/smem.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'drivers/soc/qcom') diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c index 7f38c5e11440..3102aa94aec2 100644 --- a/drivers/soc/qcom/smem.c +++ b/drivers/soc/qcom/smem.c @@ -280,7 +280,7 @@ struct qcom_smem { struct smem_region regions[0]; }; -static struct smem_private_entry * +static void * phdr_to_last_uncached_entry(struct smem_partition_header *phdr) { void *p = phdr; @@ -288,7 +288,8 @@ phdr_to_last_uncached_entry(struct smem_partition_header *phdr) return p + le32_to_cpu(phdr->offset_free_uncached); } -static void *phdr_to_first_cached_entry(struct smem_partition_header *phdr, +static struct smem_private_entry * +phdr_to_first_cached_entry(struct smem_partition_header *phdr, size_t cacheline) { void *p = phdr; @@ -297,7 +298,8 @@ static void *phdr_to_first_cached_entry(struct smem_partition_header *phdr, return p + le32_to_cpu(phdr->size) - ALIGN(sizeof(*e), cacheline); } -static void *phdr_to_last_cached_entry(struct smem_partition_header *phdr) +static void * +phdr_to_last_cached_entry(struct smem_partition_header *phdr) { void *p = phdr; -- cgit v1.2.3-55-g7522 From 04a512fea333369cc0b550f3b90df0d638e34d00 Mon Sep 17 00:00:00 2001 From: Alex Elder Date: Tue, 10 Apr 2018 17:25:39 -0500 Subject: soc: qcom: smem: byte swap values properly Two places report an error when a partition header is found to not contain the right canary value. The error messages do not properly byte swap the host ids. Fix this, and adjust the format specificier to match the 16-bit unsigned data type. Move the error handling for a bad canary value to the end of qcom_smem_alloc_private(). This avoids some long lines, and reduces the distraction of handling this unexpected problem. Signed-off-by: Alex Elder Reviewed-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/smem.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'drivers/soc/qcom') diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c index 3102aa94aec2..82f0908b90e1 100644 --- a/drivers/soc/qcom/smem.c +++ b/drivers/soc/qcom/smem.c @@ -365,13 +365,8 @@ static int qcom_smem_alloc_private(struct qcom_smem *smem, cached = phdr_to_last_cached_entry(phdr); while (hdr < end) { - if (hdr->canary != SMEM_PRIVATE_CANARY) { - dev_err(smem->dev, - "Found invalid canary in hosts %d:%d partition\n", - phdr->host0, phdr->host1); - return -EINVAL; - } - + if (hdr->canary != SMEM_PRIVATE_CANARY) + goto bad_canary; if (le16_to_cpu(hdr->item) == item) return -EEXIST; @@ -400,6 +395,11 @@ static int qcom_smem_alloc_private(struct qcom_smem *smem, le32_add_cpu(&phdr->offset_free_uncached, alloc_size); return 0; +bad_canary: + dev_err(smem->dev, "Found invalid canary in hosts %hu:%hu partition\n", + le16_to_cpu(phdr->host0), le16_to_cpu(phdr->host1)); + + return -EINVAL; } static int qcom_smem_alloc_global(struct qcom_smem *smem, @@ -563,8 +563,8 @@ static void *qcom_smem_get_private(struct qcom_smem *smem, return ERR_PTR(-ENOENT); invalid_canary: - dev_err(smem->dev, "Found invalid canary in hosts %d:%d partition\n", - phdr->host0, phdr->host1); + dev_err(smem->dev, "Found invalid canary in hosts %hu:%hu partition\n", + le16_to_cpu(phdr->host0), le16_to_cpu(phdr->host1)); return ERR_PTR(-EINVAL); } -- cgit v1.2.3-55-g7522 From 8377f8181da8dce24ace68a43e280f6a6cb2c51c Mon Sep 17 00:00:00 2001 From: Alex Elder Date: Tue, 10 Apr 2018 17:25:40 -0500 Subject: soc: qcom: smem: fix off-by-one error in qcom_smem_alloc_private() It's OK if the space for a newly-allocated uncached entry actually touches the free cached space boundary. It's only a problem if it would cross it. Signed-off-by: Alex Elder Reviewed-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/smem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/soc/qcom') diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c index 82f0908b90e1..0ed263055988 100644 --- a/drivers/soc/qcom/smem.c +++ b/drivers/soc/qcom/smem.c @@ -375,7 +375,7 @@ static int qcom_smem_alloc_private(struct qcom_smem *smem, /* Check that we don't grow into the cached region */ alloc_size = sizeof(*hdr) + ALIGN(size, 8); - if ((void *)hdr + alloc_size >= cached) { + if ((void *)hdr + alloc_size > cached) { dev_err(smem->dev, "Out of memory\n"); return -ENOSPC; } -- cgit v1.2.3-55-g7522 From 8fa1a21409da6abfe890f66532f9fcd8d2c25a3d Mon Sep 17 00:00:00 2001 From: Alex Elder Date: Tue, 10 Apr 2018 17:25:41 -0500 Subject: soc: qcom: smem: fix qcom_smem_set_global_partition() If there is at least one entry in the partition table, but no global entry, the qcom_smem_set_global_partition() should return an error just like it does if there are no partition table entries. It turns out the function still returns an error in this case, but it waits to do so until it has mistakenly treated the last entry in the table as if it were the global entry found. Fix the function to return immediately if no global entry is found in the table. Signed-off-by: Alex Elder Reviewed-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/smem.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'drivers/soc/qcom') diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c index 0ed263055988..6e42599b70d4 100644 --- a/drivers/soc/qcom/smem.c +++ b/drivers/soc/qcom/smem.c @@ -698,9 +698,10 @@ static u32 qcom_smem_get_item_count(struct qcom_smem *smem) static int qcom_smem_set_global_partition(struct qcom_smem *smem) { struct smem_partition_header *header; - struct smem_ptable_entry *entry = NULL; + struct smem_ptable_entry *entry; struct smem_ptable *ptable; u32 host0, host1, size; + bool found = false; int i; ptable = qcom_smem_get_ptable(smem); @@ -712,11 +713,13 @@ static int qcom_smem_set_global_partition(struct qcom_smem *smem) host0 = le16_to_cpu(entry->host0); host1 = le16_to_cpu(entry->host1); - if (host0 == SMEM_GLOBAL_HOST && host0 == host1) + if (host0 == SMEM_GLOBAL_HOST && host0 == host1) { + found = true; break; + } } - if (!entry) { + if (!found) { dev_err(smem->dev, "Missing entry for global partition\n"); return -EINVAL; } -- cgit v1.2.3-55-g7522 From 11d2e7edac6ad221eaf14fdc9ea2f0ddf950a839 Mon Sep 17 00:00:00 2001 From: Alex Elder Date: Tue, 10 Apr 2018 17:25:42 -0500 Subject: soc: qcom: smem: check sooner in qcom_smem_set_global_partition() There's no sense in scanning the partition table again if we know the global partition has already been discovered. Check for a non-null global_partition pointer in qcom_smem_set_global_partition() immediately. Signed-off-by: Alex Elder Reviewed-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/smem.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers/soc/qcom') diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c index 6e42599b70d4..7d9a43da5084 100644 --- a/drivers/soc/qcom/smem.c +++ b/drivers/soc/qcom/smem.c @@ -364,6 +364,11 @@ static int qcom_smem_alloc_private(struct qcom_smem *smem, end = phdr_to_last_uncached_entry(phdr); cached = phdr_to_last_cached_entry(phdr); + if (smem->global_partition) { + dev_err(smem->dev, "Already found the global partition\n"); + return -EINVAL; + } + while (hdr < end) { if (hdr->canary != SMEM_PRIVATE_CANARY) goto bad_canary; @@ -729,11 +734,6 @@ static int qcom_smem_set_global_partition(struct qcom_smem *smem) return -EINVAL; } - if (smem->global_partition) { - dev_err(smem->dev, "Already found the global partition\n"); - return -EINVAL; - } - header = smem->regions[0].virt_base + le32_to_cpu(entry->offset); host0 = le16_to_cpu(header->host0); host1 = le16_to_cpu(header->host1); -- cgit v1.2.3-55-g7522 From 7df5ff258bd27900d516fea88da10d05602bf8c7 Mon Sep 17 00:00:00 2001 From: Alex Elder Date: Fri, 27 Apr 2018 09:08:17 -0500 Subject: soc: qcom: qmi: fix a buffer sizing bug In qmi_handle_init(), a buffer is allocated for to hold messages received through the handle's socket. Any "normal" messages (expected by the caller) will have a header prepended, so the buffer size is adjusted to accomodate that. The buffer must also be of sufficient size to receive control messages, so the size is increased if necessary to ensure these will fit. Unfortunately the calculation is done wrong, making it possible for the calculated buffer size to be too small to hold a "normal" message. Specifically, if: recv_buf_size > sizeof(struct qrtr_ctrl_pkt) - sizeof(struct qmi_header) AND recv_buf_size < sizeof(struct qrtr_ctrl_pkt) the current logic will use sizeof(struct qrtr_ctrl_pkt) as the receive buffer size, which is not enough to hold the maximum "normal" message plus its header. Currently this problem occurs for (13 < recv_buf_size < 20). This patch corrects this. Signed-off-by: Alex Elder Reviewed-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/qmi_interface.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers/soc/qcom') diff --git a/drivers/soc/qcom/qmi_interface.c b/drivers/soc/qcom/qmi_interface.c index 321982277697..938ca41c56cd 100644 --- a/drivers/soc/qcom/qmi_interface.c +++ b/drivers/soc/qcom/qmi_interface.c @@ -639,10 +639,11 @@ int qmi_handle_init(struct qmi_handle *qmi, size_t recv_buf_size, if (ops) qmi->ops = *ops; + /* Make room for the header */ + recv_buf_size += sizeof(struct qmi_header); + /* Must also be sufficient to hold a control packet */ if (recv_buf_size < sizeof(struct qrtr_ctrl_pkt)) recv_buf_size = sizeof(struct qrtr_ctrl_pkt); - else - recv_buf_size += sizeof(struct qmi_header); qmi->recv_buf_size = recv_buf_size; qmi->recv_buf = kzalloc(recv_buf_size, GFP_KERNEL); -- cgit v1.2.3-55-g7522 From 6d361c1db7b69fddf5748cf212169ab57bb13a6e Mon Sep 17 00:00:00 2001 From: Alex Elder Date: Wed, 25 Apr 2018 10:18:20 -0500 Subject: soc: qcom: smem: introduce qcom_smem_virt_to_phys() Create function qcom_smem_virt_to_phys(), which returns the physical address corresponding to a given SMEM item's virtual address. This feature is required for a driver that will soon be out for review. Signed-off-by: Alex Elder Reviewed-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/smem.c | 27 +++++++++++++++++++++++++++ include/linux/soc/qcom/smem.h | 2 ++ 2 files changed, 29 insertions(+) (limited to 'drivers/soc/qcom') diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c index 7d9a43da5084..70b2ee80d6bd 100644 --- a/drivers/soc/qcom/smem.c +++ b/drivers/soc/qcom/smem.c @@ -655,6 +655,33 @@ int qcom_smem_get_free_space(unsigned host) } EXPORT_SYMBOL(qcom_smem_get_free_space); +/** + * qcom_smem_virt_to_phys() - return the physical address associated + * with an smem item pointer (previously returned by qcom_smem_get() + * @p: the virtual address to convert + * + * Returns 0 if the pointer provided is not within any smem region. + */ +phys_addr_t qcom_smem_virt_to_phys(void *p) +{ + unsigned i; + + for (i = 0; i < __smem->num_regions; i++) { + struct smem_region *region = &__smem->regions[i]; + + if (p < region->virt_base) + continue; + if (p < region->virt_base + region->size) { + u64 offset = p - region->virt_base; + + return (phys_addr_t)region->aux_base + offset; + } + } + + return 0; +} +EXPORT_SYMBOL(qcom_smem_virt_to_phys); + static int qcom_smem_get_sbl_version(struct qcom_smem *smem) { struct smem_header *header; diff --git a/include/linux/soc/qcom/smem.h b/include/linux/soc/qcom/smem.h index c1657ed27b30..86e1b358688a 100644 --- a/include/linux/soc/qcom/smem.h +++ b/include/linux/soc/qcom/smem.h @@ -9,4 +9,6 @@ void *qcom_smem_get(unsigned host, unsigned item, size_t *size); int qcom_smem_get_free_space(unsigned host); +phys_addr_t qcom_smem_virt_to_phys(void *p); + #endif -- cgit v1.2.3-55-g7522