diff options
Diffstat (limited to 'drivers/net/ipa/ipa_main.c')
-rw-r--r-- | drivers/net/ipa/ipa_main.c | 1017 |
1 files changed, 1017 insertions, 0 deletions
diff --git a/drivers/net/ipa/ipa_main.c b/drivers/net/ipa/ipa_main.c new file mode 100644 index 0000000000..da853353a5 --- /dev/null +++ b/drivers/net/ipa/ipa_main.c @@ -0,0 +1,1017 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. + * Copyright (C) 2018-2023 Linaro Ltd. + */ + +#include <linux/types.h> +#include <linux/atomic.h> +#include <linux/bitfield.h> +#include <linux/device.h> +#include <linux/bug.h> +#include <linux/io.h> +#include <linux/firmware.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/firmware/qcom/qcom_scm.h> +#include <linux/soc/qcom/mdt_loader.h> + +#include "ipa.h" +#include "ipa_power.h" +#include "ipa_data.h" +#include "ipa_endpoint.h" +#include "ipa_resource.h" +#include "ipa_cmd.h" +#include "ipa_reg.h" +#include "ipa_mem.h" +#include "ipa_table.h" +#include "ipa_smp2p.h" +#include "ipa_modem.h" +#include "ipa_uc.h" +#include "ipa_interrupt.h" +#include "gsi_trans.h" +#include "ipa_sysfs.h" + +/** + * DOC: The IP Accelerator + * + * This driver supports the Qualcomm IP Accelerator (IPA), which is a + * networking component found in many Qualcomm SoCs. The IPA is connected + * to the application processor (AP), but is also connected (and partially + * controlled by) other "execution environments" (EEs), such as a modem. + * + * The IPA is the conduit between the AP and the modem that carries network + * traffic. This driver presents a network interface representing the + * connection of the modem to external (e.g. LTE) networks. + * + * The IPA provides protocol checksum calculation, offloading this work + * from the AP. The IPA offers additional functionality, including routing, + * filtering, and NAT support, but that more advanced functionality is not + * currently supported. Despite that, some resources--including routing + * tables and filter tables--are defined in this driver because they must + * be initialized even when the advanced hardware features are not used. + * + * There are two distinct layers that implement the IPA hardware, and this + * is reflected in the organization of the driver. The generic software + * interface (GSI) is an integral component of the IPA, providing a + * well-defined communication layer between the AP subsystem and the IPA + * core. The GSI implements a set of "channels" used for communication + * between the AP and the IPA. + * + * The IPA layer uses GSI channels to implement its "endpoints". And while + * a GSI channel carries data between the AP and the IPA, a pair of IPA + * endpoints is used to carry traffic between two EEs. Specifically, the main + * modem network interface is implemented by two pairs of endpoints: a TX + * endpoint on the AP coupled with an RX endpoint on the modem; and another + * RX endpoint on the AP receiving data from a TX endpoint on the modem. + */ + +/* The name of the GSI firmware file relative to /lib/firmware */ +#define IPA_FW_PATH_DEFAULT "ipa_fws.mdt" +#define IPA_PAS_ID 15 + +/* Shift of 19.2 MHz timestamp to achieve lower resolution timestamps */ +#define DPL_TIMESTAMP_SHIFT 14 /* ~1.172 kHz, ~853 usec per tick */ +#define TAG_TIMESTAMP_SHIFT 14 +#define NAT_TIMESTAMP_SHIFT 24 /* ~1.144 Hz, ~874 msec per tick */ + +/* Divider for 19.2 MHz crystal oscillator clock to get common timer clock */ +#define IPA_XO_CLOCK_DIVIDER 192 /* 1 is subtracted where used */ + +/** + * enum ipa_firmware_loader: How GSI firmware gets loaded + * + * @IPA_LOADER_DEFER: System not ready; try again later + * @IPA_LOADER_SELF: AP loads GSI firmware + * @IPA_LOADER_MODEM: Modem loads GSI firmware, signals when done + * @IPA_LOADER_SKIP: Neither AP nor modem need to load GSI firmware + * @IPA_LOADER_INVALID: GSI firmware loader specification is invalid + */ +enum ipa_firmware_loader { + IPA_LOADER_DEFER, + IPA_LOADER_SELF, + IPA_LOADER_MODEM, + IPA_LOADER_SKIP, + IPA_LOADER_INVALID, +}; + +/** + * ipa_setup() - Set up IPA hardware + * @ipa: IPA pointer + * + * Perform initialization that requires issuing immediate commands on + * the command TX endpoint. If the modem is doing GSI firmware load + * and initialization, this function will be called when an SMP2P + * interrupt has been signaled by the modem. Otherwise it will be + * called from ipa_probe() after GSI firmware has been successfully + * loaded, authenticated, and started by Trust Zone. + */ +int ipa_setup(struct ipa *ipa) +{ + struct ipa_endpoint *exception_endpoint; + struct ipa_endpoint *command_endpoint; + struct device *dev = &ipa->pdev->dev; + int ret; + + ret = gsi_setup(&ipa->gsi); + if (ret) + return ret; + + ret = ipa_power_setup(ipa); + if (ret) + goto err_gsi_teardown; + + ipa_endpoint_setup(ipa); + + /* We need to use the AP command TX endpoint to perform other + * initialization, so we enable first. + */ + command_endpoint = ipa->name_map[IPA_ENDPOINT_AP_COMMAND_TX]; + ret = ipa_endpoint_enable_one(command_endpoint); + if (ret) + goto err_endpoint_teardown; + + ret = ipa_mem_setup(ipa); /* No matching teardown required */ + if (ret) + goto err_command_disable; + + ret = ipa_table_setup(ipa); /* No matching teardown required */ + if (ret) + goto err_command_disable; + + /* Enable the exception handling endpoint, and tell the hardware + * to use it by default. + */ + exception_endpoint = ipa->name_map[IPA_ENDPOINT_AP_LAN_RX]; + ret = ipa_endpoint_enable_one(exception_endpoint); + if (ret) + goto err_command_disable; + + ipa_endpoint_default_route_set(ipa, exception_endpoint->endpoint_id); + + /* We're all set. Now prepare for communication with the modem */ + ret = ipa_qmi_setup(ipa); + if (ret) + goto err_default_route_clear; + + ipa->setup_complete = true; + + dev_info(dev, "IPA driver setup completed successfully\n"); + + return 0; + +err_default_route_clear: + ipa_endpoint_default_route_clear(ipa); + ipa_endpoint_disable_one(exception_endpoint); +err_command_disable: + ipa_endpoint_disable_one(command_endpoint); +err_endpoint_teardown: + ipa_endpoint_teardown(ipa); + ipa_power_teardown(ipa); +err_gsi_teardown: + gsi_teardown(&ipa->gsi); + + return ret; +} + +/** + * ipa_teardown() - Inverse of ipa_setup() + * @ipa: IPA pointer + */ +static void ipa_teardown(struct ipa *ipa) +{ + struct ipa_endpoint *exception_endpoint; + struct ipa_endpoint *command_endpoint; + + /* We're going to tear everything down, as if setup never completed */ + ipa->setup_complete = false; + + ipa_qmi_teardown(ipa); + ipa_endpoint_default_route_clear(ipa); + exception_endpoint = ipa->name_map[IPA_ENDPOINT_AP_LAN_RX]; + ipa_endpoint_disable_one(exception_endpoint); + command_endpoint = ipa->name_map[IPA_ENDPOINT_AP_COMMAND_TX]; + ipa_endpoint_disable_one(command_endpoint); + ipa_endpoint_teardown(ipa); + ipa_power_teardown(ipa); + gsi_teardown(&ipa->gsi); +} + +static void +ipa_hardware_config_bcr(struct ipa *ipa, const struct ipa_data *data) +{ + const struct reg *reg; + u32 val; + + /* IPA v4.5+ has no backward compatibility register */ + if (ipa->version >= IPA_VERSION_4_5) + return; + + reg = ipa_reg(ipa, IPA_BCR); + val = data->backward_compat; + iowrite32(val, ipa->reg_virt + reg_offset(reg)); +} + +static void ipa_hardware_config_tx(struct ipa *ipa) +{ + enum ipa_version version = ipa->version; + const struct reg *reg; + u32 offset; + u32 val; + + if (version <= IPA_VERSION_4_0 || version >= IPA_VERSION_4_5) + return; + + /* Disable PA mask to allow HOLB drop */ + reg = ipa_reg(ipa, IPA_TX_CFG); + offset = reg_offset(reg); + + val = ioread32(ipa->reg_virt + offset); + + val &= ~reg_bit(reg, PA_MASK_EN); + + iowrite32(val, ipa->reg_virt + offset); +} + +static void ipa_hardware_config_clkon(struct ipa *ipa) +{ + enum ipa_version version = ipa->version; + const struct reg *reg; + u32 val; + + if (version >= IPA_VERSION_4_5) + return; + + if (version < IPA_VERSION_4_0 && version != IPA_VERSION_3_1) + return; + + /* Implement some hardware workarounds */ + reg = ipa_reg(ipa, CLKON_CFG); + if (version == IPA_VERSION_3_1) { + /* Disable MISC clock gating */ + val = reg_bit(reg, CLKON_MISC); + } else { /* IPA v4.0+ */ + /* Enable open global clocks in the CLKON configuration */ + val = reg_bit(reg, CLKON_GLOBAL); + val |= reg_bit(reg, GLOBAL_2X_CLK); + } + + iowrite32(val, ipa->reg_virt + reg_offset(reg)); +} + +/* Configure bus access behavior for IPA components */ +static void ipa_hardware_config_comp(struct ipa *ipa) +{ + const struct reg *reg; + u32 offset; + u32 val; + + /* Nothing to configure prior to IPA v4.0 */ + if (ipa->version < IPA_VERSION_4_0) + return; + + reg = ipa_reg(ipa, COMP_CFG); + offset = reg_offset(reg); + + val = ioread32(ipa->reg_virt + offset); + + if (ipa->version == IPA_VERSION_4_0) { + val &= ~reg_bit(reg, IPA_QMB_SELECT_CONS_EN); + val &= ~reg_bit(reg, IPA_QMB_SELECT_PROD_EN); + val &= ~reg_bit(reg, IPA_QMB_SELECT_GLOBAL_EN); + } else if (ipa->version < IPA_VERSION_4_5) { + val |= reg_bit(reg, GSI_MULTI_AXI_MASTERS_DIS); + } else { + /* For IPA v4.5+ FULL_FLUSH_WAIT_RS_CLOSURE_EN is 0 */ + } + + val |= reg_bit(reg, GSI_MULTI_INORDER_RD_DIS); + val |= reg_bit(reg, GSI_MULTI_INORDER_WR_DIS); + + iowrite32(val, ipa->reg_virt + offset); +} + +/* Configure DDR and (possibly) PCIe max read/write QSB values */ +static void +ipa_hardware_config_qsb(struct ipa *ipa, const struct ipa_data *data) +{ + const struct ipa_qsb_data *data0; + const struct ipa_qsb_data *data1; + const struct reg *reg; + u32 val; + + /* QMB 0 represents DDR; QMB 1 (if present) represents PCIe */ + data0 = &data->qsb_data[IPA_QSB_MASTER_DDR]; + if (data->qsb_count > 1) + data1 = &data->qsb_data[IPA_QSB_MASTER_PCIE]; + + /* Max outstanding write accesses for QSB masters */ + reg = ipa_reg(ipa, QSB_MAX_WRITES); + + val = reg_encode(reg, GEN_QMB_0_MAX_WRITES, data0->max_writes); + if (data->qsb_count > 1) + val |= reg_encode(reg, GEN_QMB_1_MAX_WRITES, data1->max_writes); + + iowrite32(val, ipa->reg_virt + reg_offset(reg)); + + /* Max outstanding read accesses for QSB masters */ + reg = ipa_reg(ipa, QSB_MAX_READS); + + val = reg_encode(reg, GEN_QMB_0_MAX_READS, data0->max_reads); + if (ipa->version >= IPA_VERSION_4_0) + val |= reg_encode(reg, GEN_QMB_0_MAX_READS_BEATS, + data0->max_reads_beats); + if (data->qsb_count > 1) { + val = reg_encode(reg, GEN_QMB_1_MAX_READS, data1->max_reads); + if (ipa->version >= IPA_VERSION_4_0) + val |= reg_encode(reg, GEN_QMB_1_MAX_READS_BEATS, + data1->max_reads_beats); + } + + iowrite32(val, ipa->reg_virt + reg_offset(reg)); +} + +/* The internal inactivity timer clock is used for the aggregation timer */ +#define TIMER_FREQUENCY 32000 /* 32 KHz inactivity timer clock */ + +/* Compute the value to use in the COUNTER_CFG register AGGR_GRANULARITY + * field to represent the given number of microseconds. The value is one + * less than the number of timer ticks in the requested period. 0 is not + * a valid granularity value (so for example @usec must be at least 16 for + * a TIMER_FREQUENCY of 32000). + */ +static __always_inline u32 ipa_aggr_granularity_val(u32 usec) +{ + return DIV_ROUND_CLOSEST(usec * TIMER_FREQUENCY, USEC_PER_SEC) - 1; +} + +/* IPA uses unified Qtime starting at IPA v4.5, implementing various + * timestamps and timers independent of the IPA core clock rate. The + * Qtimer is based on a 56-bit timestamp incremented at each tick of + * a 19.2 MHz SoC crystal oscillator (XO clock). + * + * For IPA timestamps (tag, NAT, data path logging) a lower resolution + * timestamp is achieved by shifting the Qtimer timestamp value right + * some number of bits to produce the low-order bits of the coarser + * granularity timestamp. + * + * For timers, a common timer clock is derived from the XO clock using + * a divider (we use 192, to produce a 100kHz timer clock). From + * this common clock, three "pulse generators" are used to produce + * timer ticks at a configurable frequency. IPA timers (such as + * those used for aggregation or head-of-line block handling) now + * define their period based on one of these pulse generators. + */ +static void ipa_qtime_config(struct ipa *ipa) +{ + const struct reg *reg; + u32 offset; + u32 val; + + /* Timer clock divider must be disabled when we change the rate */ + reg = ipa_reg(ipa, TIMERS_XO_CLK_DIV_CFG); + iowrite32(0, ipa->reg_virt + reg_offset(reg)); + + reg = ipa_reg(ipa, QTIME_TIMESTAMP_CFG); + /* Set DPL time stamp resolution to use Qtime (instead of 1 msec) */ + val = reg_encode(reg, DPL_TIMESTAMP_LSB, DPL_TIMESTAMP_SHIFT); + val |= reg_bit(reg, DPL_TIMESTAMP_SEL); + /* Configure tag and NAT Qtime timestamp resolution as well */ + val = reg_encode(reg, TAG_TIMESTAMP_LSB, TAG_TIMESTAMP_SHIFT); + val = reg_encode(reg, NAT_TIMESTAMP_LSB, NAT_TIMESTAMP_SHIFT); + + iowrite32(val, ipa->reg_virt + reg_offset(reg)); + + /* Set granularity of pulse generators used for other timers */ + reg = ipa_reg(ipa, TIMERS_PULSE_GRAN_CFG); + val = reg_encode(reg, PULSE_GRAN_0, IPA_GRAN_100_US); + val |= reg_encode(reg, PULSE_GRAN_1, IPA_GRAN_1_MS); + if (ipa->version >= IPA_VERSION_5_0) { + val |= reg_encode(reg, PULSE_GRAN_2, IPA_GRAN_10_MS); + val |= reg_encode(reg, PULSE_GRAN_3, IPA_GRAN_10_MS); + } else { + val |= reg_encode(reg, PULSE_GRAN_2, IPA_GRAN_1_MS); + } + + iowrite32(val, ipa->reg_virt + reg_offset(reg)); + + /* Actual divider is 1 more than value supplied here */ + reg = ipa_reg(ipa, TIMERS_XO_CLK_DIV_CFG); + offset = reg_offset(reg); + + val = reg_encode(reg, DIV_VALUE, IPA_XO_CLOCK_DIVIDER - 1); + + iowrite32(val, ipa->reg_virt + offset); + + /* Divider value is set; re-enable the common timer clock divider */ + val |= reg_bit(reg, DIV_ENABLE); + + iowrite32(val, ipa->reg_virt + offset); +} + +/* Before IPA v4.5 timing is controlled by a counter register */ +static void ipa_hardware_config_counter(struct ipa *ipa) +{ + u32 granularity = ipa_aggr_granularity_val(IPA_AGGR_GRANULARITY); + const struct reg *reg; + u32 val; + + reg = ipa_reg(ipa, COUNTER_CFG); + /* If defined, EOT_COAL_GRANULARITY is 0 */ + val = reg_encode(reg, AGGR_GRANULARITY, granularity); + iowrite32(val, ipa->reg_virt + reg_offset(reg)); +} + +static void ipa_hardware_config_timing(struct ipa *ipa) +{ + if (ipa->version < IPA_VERSION_4_5) + ipa_hardware_config_counter(ipa); + else + ipa_qtime_config(ipa); +} + +static void ipa_hardware_config_hashing(struct ipa *ipa) +{ + const struct reg *reg; + + /* Other than IPA v4.2, all versions enable "hashing". Starting + * with IPA v5.0, the filter and router tables are implemented + * differently, but the default configuration enables this feature + * (now referred to as "cacheing"), so there's nothing to do here. + */ + if (ipa->version != IPA_VERSION_4_2) + return; + + /* IPA v4.2 does not support hashed tables, so disable them */ + reg = ipa_reg(ipa, FILT_ROUT_HASH_EN); + + /* IPV6_ROUTER_HASH, IPV6_FILTER_HASH, IPV4_ROUTER_HASH, + * IPV4_FILTER_HASH are all zero. + */ + iowrite32(0, ipa->reg_virt + reg_offset(reg)); +} + +static void ipa_idle_indication_cfg(struct ipa *ipa, + u32 enter_idle_debounce_thresh, + bool const_non_idle_enable) +{ + const struct reg *reg; + u32 val; + + if (ipa->version < IPA_VERSION_3_5_1) + return; + + reg = ipa_reg(ipa, IDLE_INDICATION_CFG); + val = reg_encode(reg, ENTER_IDLE_DEBOUNCE_THRESH, + enter_idle_debounce_thresh); + if (const_non_idle_enable) + val |= reg_bit(reg, CONST_NON_IDLE_ENABLE); + + iowrite32(val, ipa->reg_virt + reg_offset(reg)); +} + +/** + * ipa_hardware_dcd_config() - Enable dynamic clock division on IPA + * @ipa: IPA pointer + * + * Configures when the IPA signals it is idle to the global clock + * controller, which can respond by scaling down the clock to save + * power. + */ +static void ipa_hardware_dcd_config(struct ipa *ipa) +{ + /* Recommended values for IPA 3.5 and later according to IPA HPG */ + ipa_idle_indication_cfg(ipa, 256, false); +} + +static void ipa_hardware_dcd_deconfig(struct ipa *ipa) +{ + /* Power-on reset values */ + ipa_idle_indication_cfg(ipa, 0, true); +} + +/** + * ipa_hardware_config() - Primitive hardware initialization + * @ipa: IPA pointer + * @data: IPA configuration data + */ +static void ipa_hardware_config(struct ipa *ipa, const struct ipa_data *data) +{ + ipa_hardware_config_bcr(ipa, data); + ipa_hardware_config_tx(ipa); + ipa_hardware_config_clkon(ipa); + ipa_hardware_config_comp(ipa); + ipa_hardware_config_qsb(ipa, data); + ipa_hardware_config_timing(ipa); + ipa_hardware_config_hashing(ipa); + ipa_hardware_dcd_config(ipa); +} + +/** + * ipa_hardware_deconfig() - Inverse of ipa_hardware_config() + * @ipa: IPA pointer + * + * This restores the power-on reset values (even if they aren't different) + */ +static void ipa_hardware_deconfig(struct ipa *ipa) +{ + /* Mostly we just leave things as we set them. */ + ipa_hardware_dcd_deconfig(ipa); +} + +/** + * ipa_config() - Configure IPA hardware + * @ipa: IPA pointer + * @data: IPA configuration data + * + * Perform initialization requiring IPA power to be enabled. + */ +static int ipa_config(struct ipa *ipa, const struct ipa_data *data) +{ + int ret; + + ipa_hardware_config(ipa, data); + + ret = ipa_mem_config(ipa); + if (ret) + goto err_hardware_deconfig; + + ipa->interrupt = ipa_interrupt_config(ipa); + if (IS_ERR(ipa->interrupt)) { + ret = PTR_ERR(ipa->interrupt); + ipa->interrupt = NULL; + goto err_mem_deconfig; + } + + ipa_uc_config(ipa); + + ret = ipa_endpoint_config(ipa); + if (ret) + goto err_uc_deconfig; + + ipa_table_config(ipa); /* No deconfig required */ + + /* Assign resource limitation to each group; no deconfig required */ + ret = ipa_resource_config(ipa, data->resource_data); + if (ret) + goto err_endpoint_deconfig; + + ret = ipa_modem_config(ipa); + if (ret) + goto err_endpoint_deconfig; + + return 0; + +err_endpoint_deconfig: + ipa_endpoint_deconfig(ipa); +err_uc_deconfig: + ipa_uc_deconfig(ipa); + ipa_interrupt_deconfig(ipa->interrupt); + ipa->interrupt = NULL; +err_mem_deconfig: + ipa_mem_deconfig(ipa); +err_hardware_deconfig: + ipa_hardware_deconfig(ipa); + + return ret; +} + +/** + * ipa_deconfig() - Inverse of ipa_config() + * @ipa: IPA pointer + */ +static void ipa_deconfig(struct ipa *ipa) +{ + ipa_modem_deconfig(ipa); + ipa_endpoint_deconfig(ipa); + ipa_uc_deconfig(ipa); + ipa_interrupt_deconfig(ipa->interrupt); + ipa->interrupt = NULL; + ipa_mem_deconfig(ipa); + ipa_hardware_deconfig(ipa); +} + +static int ipa_firmware_load(struct device *dev) +{ + const struct firmware *fw; + struct device_node *node; + struct resource res; + phys_addr_t phys; + const char *path; + ssize_t size; + void *virt; + int ret; + + node = of_parse_phandle(dev->of_node, "memory-region", 0); + if (!node) { + dev_err(dev, "DT error getting \"memory-region\" property\n"); + return -EINVAL; + } + + ret = of_address_to_resource(node, 0, &res); + of_node_put(node); + if (ret) { + dev_err(dev, "error %d getting \"memory-region\" resource\n", + ret); + return ret; + } + + /* Use name from DTB if specified; use default for *any* error */ + ret = of_property_read_string(dev->of_node, "firmware-name", &path); + if (ret) { + dev_dbg(dev, "error %d getting \"firmware-name\" resource\n", + ret); + path = IPA_FW_PATH_DEFAULT; + } + + ret = request_firmware(&fw, path, dev); + if (ret) { + dev_err(dev, "error %d requesting \"%s\"\n", ret, path); + return ret; + } + + phys = res.start; + size = (size_t)resource_size(&res); + virt = memremap(phys, size, MEMREMAP_WC); + if (!virt) { + dev_err(dev, "unable to remap firmware memory\n"); + ret = -ENOMEM; + goto out_release_firmware; + } + + ret = qcom_mdt_load(dev, fw, path, IPA_PAS_ID, virt, phys, size, NULL); + if (ret) + dev_err(dev, "error %d loading \"%s\"\n", ret, path); + else if ((ret = qcom_scm_pas_auth_and_reset(IPA_PAS_ID))) + dev_err(dev, "error %d authenticating \"%s\"\n", ret, path); + + memunmap(virt); +out_release_firmware: + release_firmware(fw); + + return ret; +} + +static const struct of_device_id ipa_match[] = { + { + .compatible = "qcom,msm8998-ipa", + .data = &ipa_data_v3_1, + }, + { + .compatible = "qcom,sdm845-ipa", + .data = &ipa_data_v3_5_1, + }, + { + .compatible = "qcom,sc7180-ipa", + .data = &ipa_data_v4_2, + }, + { + .compatible = "qcom,sdx55-ipa", + .data = &ipa_data_v4_5, + }, + { + .compatible = "qcom,sm6350-ipa", + .data = &ipa_data_v4_7, + }, + { + .compatible = "qcom,sm8350-ipa", + .data = &ipa_data_v4_9, + }, + { + .compatible = "qcom,sc7280-ipa", + .data = &ipa_data_v4_11, + }, + { + .compatible = "qcom,sdx65-ipa", + .data = &ipa_data_v5_0, + }, + { }, +}; +MODULE_DEVICE_TABLE(of, ipa_match); + +/* Check things that can be validated at build time. This just + * groups these things BUILD_BUG_ON() calls don't clutter the rest + * of the code. + * */ +static void ipa_validate_build(void) +{ + /* At one time we assumed a 64-bit build, allowing some do_div() + * calls to be replaced by simple division or modulo operations. + * We currently only perform divide and modulo operations on u32, + * u16, or size_t objects, and of those only size_t has any chance + * of being a 64-bit value. (It should be guaranteed 32 bits wide + * on a 32-bit build, but there is no harm in verifying that.) + */ + BUILD_BUG_ON(!IS_ENABLED(CONFIG_64BIT) && sizeof(size_t) != 4); + + /* Code assumes the EE ID for the AP is 0 (zeroed structure field) */ + BUILD_BUG_ON(GSI_EE_AP != 0); + + /* There's no point if we have no channels or event rings */ + BUILD_BUG_ON(!GSI_CHANNEL_COUNT_MAX); + BUILD_BUG_ON(!GSI_EVT_RING_COUNT_MAX); + + /* GSI hardware design limits */ + BUILD_BUG_ON(GSI_CHANNEL_COUNT_MAX > 32); + BUILD_BUG_ON(GSI_EVT_RING_COUNT_MAX > 31); + + /* The number of TREs in a transaction is limited by the channel's + * TLV FIFO size. A transaction structure uses 8-bit fields + * to represents the number of TREs it has allocated and used. + */ + BUILD_BUG_ON(GSI_TLV_MAX > U8_MAX); + + /* This is used as a divisor */ + BUILD_BUG_ON(!IPA_AGGR_GRANULARITY); + + /* Aggregation granularity value can't be 0, and must fit */ + BUILD_BUG_ON(!ipa_aggr_granularity_val(IPA_AGGR_GRANULARITY)); +} + +static enum ipa_firmware_loader ipa_firmware_loader(struct device *dev) +{ + bool modem_init; + const char *str; + int ret; + + /* Look up the old and new properties by name */ + modem_init = of_property_read_bool(dev->of_node, "modem-init"); + ret = of_property_read_string(dev->of_node, "qcom,gsi-loader", &str); + + /* If the new property doesn't exist, it's legacy behavior */ + if (ret == -EINVAL) { + if (modem_init) + return IPA_LOADER_MODEM; + goto out_self; + } + + /* Any other error on the new property means it's poorly defined */ + if (ret) + return IPA_LOADER_INVALID; + + /* New property value exists; if old one does too, that's invalid */ + if (modem_init) + return IPA_LOADER_INVALID; + + /* Modem loads GSI firmware for "modem" */ + if (!strcmp(str, "modem")) + return IPA_LOADER_MODEM; + + /* No GSI firmware load is needed for "skip" */ + if (!strcmp(str, "skip")) + return IPA_LOADER_SKIP; + + /* Any value other than "self" is an error */ + if (strcmp(str, "self")) + return IPA_LOADER_INVALID; +out_self: + /* We need Trust Zone to load firmware; make sure it's available */ + if (qcom_scm_is_available()) + return IPA_LOADER_SELF; + + return IPA_LOADER_DEFER; +} + +/** + * ipa_probe() - IPA platform driver probe function + * @pdev: Platform device pointer + * + * Return: 0 if successful, or a negative error code (possibly + * EPROBE_DEFER) + * + * This is the main entry point for the IPA driver. Initialization proceeds + * in several stages: + * - The "init" stage involves activities that can be initialized without + * access to the IPA hardware. + * - The "config" stage requires IPA power to be active so IPA registers + * can be accessed, but does not require the use of IPA immediate commands. + * - The "setup" stage uses IPA immediate commands, and so requires the GSI + * layer to be initialized. + * + * A Boolean Device Tree "modem-init" property determines whether GSI + * initialization will be performed by the AP (Trust Zone) or the modem. + * If the AP does GSI initialization, the setup phase is entered after + * this has completed successfully. Otherwise the modem initializes + * the GSI layer and signals it has finished by sending an SMP2P interrupt + * to the AP; this triggers the start if IPA setup. + */ +static int ipa_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + enum ipa_firmware_loader loader; + const struct ipa_data *data; + struct ipa_power *power; + struct ipa *ipa; + int ret; + + ipa_validate_build(); + + /* Get configuration data early; needed for power initialization */ + data = of_device_get_match_data(dev); + if (!data) { + dev_err(dev, "matched hardware not supported\n"); + return -ENODEV; + } + + if (!ipa_version_supported(data->version)) { + dev_err(dev, "unsupported IPA version %u\n", data->version); + return -EINVAL; + } + + if (!data->modem_route_count) { + dev_err(dev, "modem_route_count cannot be zero\n"); + return -EINVAL; + } + + loader = ipa_firmware_loader(dev); + if (loader == IPA_LOADER_INVALID) + return -EINVAL; + if (loader == IPA_LOADER_DEFER) + return -EPROBE_DEFER; + + /* The clock and interconnects might not be ready when we're + * probed, so might return -EPROBE_DEFER. + */ + power = ipa_power_init(dev, data->power_data); + if (IS_ERR(power)) + return PTR_ERR(power); + + /* No more EPROBE_DEFER. Allocate and initialize the IPA structure */ + ipa = kzalloc(sizeof(*ipa), GFP_KERNEL); + if (!ipa) { + ret = -ENOMEM; + goto err_power_exit; + } + + ipa->pdev = pdev; + dev_set_drvdata(dev, ipa); + ipa->power = power; + ipa->version = data->version; + ipa->modem_route_count = data->modem_route_count; + init_completion(&ipa->completion); + + ret = ipa_reg_init(ipa); + if (ret) + goto err_kfree_ipa; + + ret = ipa_mem_init(ipa, data->mem_data); + if (ret) + goto err_reg_exit; + + ret = gsi_init(&ipa->gsi, pdev, ipa->version, data->endpoint_count, + data->endpoint_data); + if (ret) + goto err_mem_exit; + + /* Result is a non-zero mask of endpoints that support filtering */ + ret = ipa_endpoint_init(ipa, data->endpoint_count, data->endpoint_data); + if (ret) + goto err_gsi_exit; + + ret = ipa_table_init(ipa); + if (ret) + goto err_endpoint_exit; + + ret = ipa_smp2p_init(ipa, loader == IPA_LOADER_MODEM); + if (ret) + goto err_table_exit; + + /* Power needs to be active for config and setup */ + ret = pm_runtime_get_sync(dev); + if (WARN_ON(ret < 0)) + goto err_power_put; + + ret = ipa_config(ipa, data); + if (ret) + goto err_power_put; + + dev_info(dev, "IPA driver initialized"); + + /* If the modem is loading GSI firmware, it will trigger a call to + * ipa_setup() when it has finished. In that case we're done here. + */ + if (loader == IPA_LOADER_MODEM) + goto done; + + if (loader == IPA_LOADER_SELF) { + /* The AP is loading GSI firmware; do so now */ + ret = ipa_firmware_load(dev); + if (ret) + goto err_deconfig; + } /* Otherwise loader == IPA_LOADER_SKIP */ + + /* GSI firmware is loaded; proceed to setup */ + ret = ipa_setup(ipa); + if (ret) + goto err_deconfig; +done: + pm_runtime_mark_last_busy(dev); + (void)pm_runtime_put_autosuspend(dev); + + return 0; + +err_deconfig: + ipa_deconfig(ipa); +err_power_put: + pm_runtime_put_noidle(dev); + ipa_smp2p_exit(ipa); +err_table_exit: + ipa_table_exit(ipa); +err_endpoint_exit: + ipa_endpoint_exit(ipa); +err_gsi_exit: + gsi_exit(&ipa->gsi); +err_mem_exit: + ipa_mem_exit(ipa); +err_reg_exit: + ipa_reg_exit(ipa); +err_kfree_ipa: + kfree(ipa); +err_power_exit: + ipa_power_exit(power); + + return ret; +} + +static int ipa_remove(struct platform_device *pdev) +{ + struct ipa *ipa = dev_get_drvdata(&pdev->dev); + struct ipa_power *power = ipa->power; + struct device *dev = &pdev->dev; + int ret; + + /* Prevent the modem from triggering a call to ipa_setup(). This + * also ensures a modem-initiated setup that's underway completes. + */ + ipa_smp2p_irq_disable_setup(ipa); + + ret = pm_runtime_get_sync(dev); + if (WARN_ON(ret < 0)) + goto out_power_put; + + if (ipa->setup_complete) { + ret = ipa_modem_stop(ipa); + /* If starting or stopping is in progress, try once more */ + if (ret == -EBUSY) { + usleep_range(USEC_PER_MSEC, 2 * USEC_PER_MSEC); + ret = ipa_modem_stop(ipa); + } + if (ret) + return ret; + + ipa_teardown(ipa); + } + + ipa_deconfig(ipa); +out_power_put: + pm_runtime_put_noidle(dev); + ipa_smp2p_exit(ipa); + ipa_table_exit(ipa); + ipa_endpoint_exit(ipa); + gsi_exit(&ipa->gsi); + ipa_mem_exit(ipa); + ipa_reg_exit(ipa); + kfree(ipa); + ipa_power_exit(power); + + dev_info(dev, "IPA driver removed"); + + return 0; +} + +static void ipa_shutdown(struct platform_device *pdev) +{ + int ret; + + ret = ipa_remove(pdev); + if (ret) + dev_err(&pdev->dev, "shutdown: remove returned %d\n", ret); +} + +static const struct attribute_group *ipa_attribute_groups[] = { + &ipa_attribute_group, + &ipa_feature_attribute_group, + &ipa_endpoint_id_attribute_group, + &ipa_modem_attribute_group, + NULL, +}; + +static struct platform_driver ipa_driver = { + .probe = ipa_probe, + .remove = ipa_remove, + .shutdown = ipa_shutdown, + .driver = { + .name = "ipa", + .pm = &ipa_pm_ops, + .of_match_table = ipa_match, + .dev_groups = ipa_attribute_groups, + }, +}; + +module_platform_driver(ipa_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Qualcomm IP Accelerator device driver"); |