// SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. * Copyright (C) 2018-2024 Linaro Ltd. */ #include <linux/bug.h>#include <linux/firmware.h>#include <linux/io.h>#include <linux/module.h>#include <linux/of.h>#include <linux/of_reserved_mem.h>#include <linux/platform_device.h>#include <linux/pm_runtime.h>#include <linux/types.h> #include <linux/firmware/qcom/qcom_pas.h>#include <linux/soc/qcom/mdt_loader.h> #include "ipa.h"#include "ipa_cmd.h"#include "ipa_data.h"#include "ipa_endpoint.h"#include "ipa_interrupt.h"#include "ipa_mem.h"#include "ipa_modem.h"#include "ipa_power.h"#include "ipa_reg.h"#include "ipa_resource.h"#include "ipa_smp2p.h"#include "ipa_sysfs.h"#include "ipa_table.h"#include "ipa_uc.h"#include "ipa_version.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 *//* IPA v5.5+ does not specify Qtime timestamp config for DPL */#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->dev; int ret; ret = gsi_setup(&ipa->gsi); if (ret) return ret; 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); 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); gsi_teardown(&ipa->gsi);} static voidipa_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 voidipa_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 = 0; /* 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); if (ipa->version < IPA_VERSION_5_5) { /* Set DPL time stamp resolution to use Qtime (not 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);Lines 1–500 of 1026. Load more
// SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. * Copyright (C) 2018-2024 Linaro Ltd. */ #include <linux/bug.h> #include <linux/firmware.h> #include <linux/io.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_reserved_mem.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/types.h> #include <linux/firmware/qcom/qcom_pas.h> #include <linux/soc/qcom/mdt_loader.h> #include "ipa.h" #include "ipa_cmd.h" #include "ipa_data.h" #include "ipa_endpoint.h" #include "ipa_interrupt.h" #include "ipa_mem.h" #include "ipa_modem.h" #include "ipa_power.h" #include "ipa_reg.h" #include "ipa_resource.h" #include "ipa_smp2p.h" #include "ipa_sysfs.h" #include "ipa_table.h" #include "ipa_uc.h" #include "ipa_version.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 */ /* IPA v5.5+ does not specify Qtime timestamp config for DPL */ #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->dev; int ret; ret = gsi_setup(&ipa->gsi); if (ret) return ret; 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); 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); 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 = 0; /* 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); if (ipa->version < IPA_VERSION_5_5) { /* Set DPL time stamp resolution to use Qtime (not 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);