diff options
author | 2023-02-21 18:24:12 -0800 | |
---|---|---|
committer | 2023-02-21 18:24:12 -0800 | |
commit | 5b7c4cabbb65f5c469464da6c5f614cbd7f730f2 (patch) | |
tree | cc5c2d0a898769fd59549594fedb3ee6f84e59a0 /drivers/net/ipa/ipa_main.c | |
download | linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.tar.gz linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.zip |
Merge tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-nextgrafted
Pull networking updates from Jakub Kicinski:
"Core:
- Add dedicated kmem_cache for typical/small skb->head, avoid having
to access struct page at kfree time, and improve memory use.
- Introduce sysctl to set default RPS configuration for new netdevs.
- Define Netlink protocol specification format which can be used to
describe messages used by each family and auto-generate parsers.
Add tools for generating kernel data structures and uAPI headers.
- Expose all net/core sysctls inside netns.
- Remove 4s sleep in netpoll if carrier is instantly detected on
boot.
- Add configurable limit of MDB entries per port, and port-vlan.
- Continue populating drop reasons throughout the stack.
- Retire a handful of legacy Qdiscs and classifiers.
Protocols:
- Support IPv4 big TCP (TSO frames larger than 64kB).
- Add IP_LOCAL_PORT_RANGE socket option, to control local port range
on socket by socket basis.
- Track and report in procfs number of MPTCP sockets used.
- Support mixing IPv4 and IPv6 flows in the in-kernel MPTCP path
manager.
- IPv6: don't check net.ipv6.route.max_size and rely on garbage
collection to free memory (similarly to IPv4).
- Support Penultimate Segment Pop (PSP) flavor in SRv6 (RFC8986).
- ICMP: add per-rate limit counters.
- Add support for user scanning requests in ieee802154.
- Remove static WEP support.
- Support minimal Wi-Fi 7 Extremely High Throughput (EHT) rate
reporting.
- WiFi 7 EHT channel puncturing support (client & AP).
BPF:
- Add a rbtree data structure following the "next-gen data structure"
precedent set by recently added linked list, that is, by using
kfunc + kptr instead of adding a new BPF map type.
- Expose XDP hints via kfuncs with initial support for RX hash and
timestamp metadata.
- Add BPF_F_NO_TUNNEL_KEY extension to bpf_skb_set_tunnel_key to
better support decap on GRE tunnel devices not operating in collect
metadata.
- Improve x86 JIT's codegen for PROBE_MEM runtime error checks.
- Remove the need for trace_printk_lock for bpf_trace_printk and
bpf_trace_vprintk helpers.
- Extend libbpf's bpf_tracing.h support for tracing arguments of
kprobes/uprobes and syscall as a special case.
- Significantly reduce the search time for module symbols by
livepatch and BPF.
- Enable cpumasks to be used as kptrs, which is useful for tracing
programs tracking which tasks end up running on which CPUs in
different time intervals.
- Add support for BPF trampoline on s390x and riscv64.
- Add capability to export the XDP features supported by the NIC.
- Add __bpf_kfunc tag for marking kernel functions as kfuncs.
- Add cgroup.memory=nobpf kernel parameter option to disable BPF
memory accounting for container environments.
Netfilter:
- Remove the CLUSTERIP target. It has been marked as obsolete for
years, and we still have WARN splats wrt races of the out-of-band
/proc interface installed by this target.
- Add 'destroy' commands to nf_tables. They are identical to the
existing 'delete' commands, but do not return an error if the
referenced object (set, chain, rule...) did not exist.
Driver API:
- Improve cpumask_local_spread() locality to help NICs set the right
IRQ affinity on AMD platforms.
- Separate C22 and C45 MDIO bus transactions more clearly.
- Introduce new DCB table to control DSCP rewrite on egress.
- Support configuration of Physical Layer Collision Avoidance (PLCA)
Reconciliation Sublayer (RS) (802.3cg-2019). Modern version of
shared medium Ethernet.
- Support for MAC Merge layer (IEEE 802.3-2018 clause 99). Allowing
preemption of low priority frames by high priority frames.
- Add support for controlling MACSec offload using netlink SET.
- Rework devlink instance refcounts to allow registration and
de-registration under the instance lock. Split the code into
multiple files, drop some of the unnecessarily granular locks and
factor out common parts of netlink operation handling.
- Add TX frame aggregation parameters (for USB drivers).
- Add a new attr TCA_EXT_WARN_MSG to report TC (offload) warning
messages with notifications for debug.
- Allow offloading of UDP NEW connections via act_ct.
- Add support for per action HW stats in TC.
- Support hardware miss to TC action (continue processing in SW from
a specific point in the action chain).
- Warn if old Wireless Extension user space interface is used with
modern cfg80211/mac80211 drivers. Do not support Wireless
Extensions for Wi-Fi 7 devices at all. Everyone should switch to
using nl80211 interface instead.
- Improve the CAN bit timing configuration. Use extack to return
error messages directly to user space, update the SJW handling,
including the definition of a new default value that will benefit
CAN-FD controllers, by increasing their oscillator tolerance.
New hardware / drivers:
- Ethernet:
- nVidia BlueField-3 support (control traffic driver)
- Ethernet support for imx93 SoCs
- Motorcomm yt8531 gigabit Ethernet PHY
- onsemi NCN26000 10BASE-T1S PHY (with support for PLCA)
- Microchip LAN8841 PHY (incl. cable diagnostics and PTP)
- Amlogic gxl MDIO mux
- WiFi:
- RealTek RTL8188EU (rtl8xxxu)
- Qualcomm Wi-Fi 7 devices (ath12k)
- CAN:
- Renesas R-Car V4H
Drivers:
- Bluetooth:
- Set Per Platform Antenna Gain (PPAG) for Intel controllers.
- Ethernet NICs:
- Intel (1G, igc):
- support TSN / Qbv / packet scheduling features of i226 model
- Intel (100G, ice):
- use GNSS subsystem instead of TTY
- multi-buffer XDP support
- extend support for GPIO pins to E823 devices
- nVidia/Mellanox:
- update the shared buffer configuration on PFC commands
- implement PTP adjphase function for HW offset control
- TC support for Geneve and GRE with VF tunnel offload
- more efficient crypto key management method
- multi-port eswitch support
- Netronome/Corigine:
- add DCB IEEE support
- support IPsec offloading for NFP3800
- Freescale/NXP (enetc):
- support XDP_REDIRECT for XDP non-linear buffers
- improve reconfig, avoid link flap and waiting for idle
- support MAC Merge layer
- Other NICs:
- sfc/ef100: add basic devlink support for ef100
- ionic: rx_push mode operation (writing descriptors via MMIO)
- bnxt: use the auxiliary bus abstraction for RDMA
- r8169: disable ASPM and reset bus in case of tx timeout
- cpsw: support QSGMII mode for J721e CPSW9G
- cpts: support pulse-per-second output
- ngbe: add an mdio bus driver
- usbnet: optimize usbnet_bh() by avoiding unnecessary queuing
- r8152: handle devices with FW with NCM support
- amd-xgbe: support 10Mbps, 2.5GbE speeds and rx-adaptation
- virtio-net: support multi buffer XDP
- virtio/vsock: replace virtio_vsock_pkt with sk_buff
- tsnep: XDP support
- Ethernet high-speed switches:
- nVidia/Mellanox (mlxsw):
- add support for latency TLV (in FW control messages)
- Microchip (sparx5):
- separate explicit and implicit traffic forwarding rules, make
the implicit rules always active
- add support for egress DSCP rewrite
- IS0 VCAP support (Ingress Classification)
- IS2 VCAP filters (protos, L3 addrs, L4 ports, flags, ToS
etc.)
- ES2 VCAP support (Egress Access Control)
- support for Per-Stream Filtering and Policing (802.1Q,
8.6.5.1)
- Ethernet embedded switches:
- Marvell (mv88e6xxx):
- add MAB (port auth) offload support
- enable PTP receive for mv88e6390
- NXP (ocelot):
- support MAC Merge layer
- support for the the vsc7512 internal copper phys
- Microchip:
- lan9303: convert to PHYLINK
- lan966x: support TC flower filter statistics
- lan937x: PTP support for KSZ9563/KSZ8563 and LAN937x
- lan937x: support Credit Based Shaper configuration
- ksz9477: support Energy Efficient Ethernet
- other:
- qca8k: convert to regmap read/write API, use bulk operations
- rswitch: Improve TX timestamp accuracy
- Intel WiFi (iwlwifi):
- EHT (Wi-Fi 7) rate reporting
- STEP equalizer support: transfer some STEP (connection to radio
on platforms with integrated wifi) related parameters from the
BIOS to the firmware.
- Qualcomm 802.11ax WiFi (ath11k):
- IPQ5018 support
- Fine Timing Measurement (FTM) responder role support
- channel 177 support
- MediaTek WiFi (mt76):
- per-PHY LED support
- mt7996: EHT (Wi-Fi 7) support
- Wireless Ethernet Dispatch (WED) reset support
- switch to using page pool allocator
- RealTek WiFi (rtw89):
- support new version of Bluetooth co-existance
- Mobile:
- rmnet: support TX aggregation"
* tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (1872 commits)
page_pool: add a comment explaining the fragment counter usage
net: ethtool: fix __ethtool_dev_mm_supported() implementation
ethtool: pse-pd: Fix double word in comments
xsk: add linux/vmalloc.h to xsk.c
sefltests: netdevsim: wait for devlink instance after netns removal
selftest: fib_tests: Always cleanup before exit
net/mlx5e: Align IPsec ASO result memory to be as required by hardware
net/mlx5e: TC, Set CT miss to the specific ct action instance
net/mlx5e: Rename CHAIN_TO_REG to MAPPED_OBJ_TO_REG
net/mlx5: Refactor tc miss handling to a single function
net/mlx5: Kconfig: Make tc offload depend on tc skb extension
net/sched: flower: Support hardware miss to tc action
net/sched: flower: Move filter handle initialization earlier
net/sched: cls_api: Support hardware miss to tc action
net/sched: Rename user cookie and act cookie
sfc: fix builds without CONFIG_RTC_LIB
sfc: clean up some inconsistent indentings
net/mlx4_en: Introduce flexible array to silence overflow warning
net: lan966x: Fix possible deadlock inside PTP
net/ulp: Remove redundant ->clone() test in inet_clone_ulp().
...
Diffstat (limited to 'drivers/net/ipa/ipa_main.c')
-rw-r--r-- | drivers/net/ipa/ipa_main.c | 1013 |
1 files changed, 1013 insertions, 0 deletions
diff --git a/drivers/net/ipa/ipa_main.c b/drivers/net/ipa/ipa_main.c new file mode 100644 index 000000000..6cb7bf96a --- /dev/null +++ b/drivers/net/ipa/ipa_main.c @@ -0,0 +1,1013 @@ +// 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_device.h> +#include <linux/of_address.h> +#include <linux/pm_runtime.h> +#include <linux/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, + }, + { }, +}; +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"); |