diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-09-13 12:27:00 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-09-13 12:27:00 -0700 |
| commit | 22098763a10d9c1340827fcf6edab66f153b27f0 (patch) | |
| tree | 3962f95a34df034652ff93686cdd156c7197f2d9 /drivers/phy/spacemit | |
| download | linux-stable-22098763a10d9c1340827fcf6edab66f153b27f0.tar.gz linux-stable-22098763a10d9c1340827fcf6edab66f153b27f0.zip | |
Merge tag 'trace-v7.3-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-tracegrafted
Pull tracing fixes from Steven Rostedt:
- Don't destroy user event fields when removal fails
User event fields are destroyed before the event is removed from
visibility. But that can fail leaving the still visible event with no
fields. Move the destroying of the fields to after the event is
successfully removed from visibility.
- Initialize function graph state is fork before calling
copy_exec_state()
For non-CLONE_VM forks, copy_exec_state() allocates a new
task_exec_state. If that allocation fails, ftrace_graph_exit_task()
will free the tasks ret_stack pointer. Since that pointer is still
using the parent's ret_stack, it mistakenly frees the parent's
pointer too.
Call ftrace_graph_init() on the task first which will NULL out the
new tasks's ret_stack and if the copy fails, it will not free
anything.
- Remove FGRAPH_MAX_INDEX
The macro FGRAPH_MAX_INDEX was added but never used. Remove it.
- Save ent_size in function graph printing of nested functions
The function graph tracer needs to look at the next event to see if
the next event is the return of the current function entry. If it is,
it prints a single line:
ktime_get();
Otherwise it prints it like a nested function:
tick_nohz_irq_exit() {
ktime_get();
kcpustat_irq_exit();
}
In order to look at the next event, it must save the current event so
that it has the information to print from it. It saves the event in
the iterator descriptor called "ent". What it doesn't save is the
ent_size of the event which is now used to know if the function graph
arguments are to be printed. The peek doesn't save the size so the
size used happens to be that of the size of the last event that was
seen.
Save the entry event size in the iterator descriptor so that the
correct size is used.
- Fix several errors with freeing data in the histogram code
The histogram code had a lot of leaked or or incorrect accounting
when failures happen. Correct them.
- Fix histogram regression of .percent and .graph modifiers
Up until 6.3 histogram values could have "percent" or "graph"
modifiers that changed how they were printed. But a change that added
restricting histograms values from being strings, stack traces and
other modifiers inadvertently prevented them from using the percent
and graph modifiers, which were legal use cases for values.
Put back the percent and graph modifiers.
- Fix various typos in the comments
- Set the trace_clock before initializing a histogram with clock
argument
The histogram API allows the user to specific which trace clock to
use via a "clock=" string. The histogram is set up first before the
clock is checked. If the passed in clock is not valid, it exits
without fully fixing up the histogram leaving it on the list and a
use-after-free can trigger.
Update the clock argument first and if it fails then exit gracefully
before the histogram trigger is placed on any lists.
- Restore :mod: trailer after parsing in ftrace_set_clr_event
The function ftrace_set_clr_event() modifies the parse string and
needs to put it back to what was passed in. It searches for ":mod:"
via a strsep() but fails to put back the first ':' in the string.
Add back the ':' in the passed in string.
- Take trace_array reference when opening a tracer options file
The options files are dynamically created and some tracers add their
own options. When a tracer adds their own list of options, the
trace_array holding them has an array to hold the list of options for
each tracer. This array increases in size via a krealloc(), and the
new entry gets a newly allocated array to hold the options of the new
tracer being added.
The element in each entry of the tracer's option array holds a
pointer back to the trace_array, a pointer to the tracer it is
associated to, a pointer to the flags of the option.
The issue is that these arrays are freed when the trace_array is
freed when its instance it represents is removed from the instances
directory. There's a race that an open of one of these options files
can happen when the instance is being removed.
Add a new helper function to be called by the open function of the
options file to iterate all existing trace_arrays under a lock and
find the one that has the given option element in one of it's tracer
arrays. If found, then update the associated trace_array's reference
counter to keep it from being freed. If not found, have the open call
return -ENODEV.
- Disable interrupts when acquiring the lock in rb_wake_up_waiters()
The function rb_wake_up_waiters() assumes it will be called in
interrupt context and does not disable irqs when taking
cpu_buffer->reader_lock, which can be called in hard interrupt
context. The issue is in PREEMPT_RT, this function is called in
thread context leaving this lock open to a deadlock.
Take the lock with interrupts disabled.
- Use rcu_assign_pointer() for tmp_ops filter hash
The tmp_ops used in update_ftrace_direct_mod() assigns its
filter_hash field directly, but that field is annotated as __rcu and
sparse complains. Assign it with rcu_assign_pointer()
- Fix use-after-free in enable_trigger_private_data_free()
The trace_event_call is accessed through the event_trigger_data's
trace_event_file pointer to put the trace_event_call on freeing. The
issue is that the trace_event_file data may have been freed already
causing a use-after-free. Add a field to the event_trigger_data that
points directly to the trace_event_call so that it can decrement its
reference directly without needing to go through the
trace_event_file.
- Fix accounting of buffer data remote headers
trace_buffer_desc_size() and trace_remote_alloc_buffer() undercount
the number of pages is needed for the asked for size as it doesn't
take into account the meta data on each page. Add a helper function
to do the calculation properly and use that in these functions.
- Catch nr_page_va overflow in ring_buffer_desc sizing
The number of pages per remote ring buffer is capped by
ring_buffer_desc::nr_page_va (32 bits). A buffer_size large enough to
overflow that field would silently allocate a descriptor smaller than
what was asked for.
- Do not resize the subbuf order if any per_cpu buffer is disabled
The mmapping of ring buffers disables resizing the subbuffers, but it
is done per-cpu whereas the subbuf size change is done for all the
per_cpu buffers under the buffer->mutex. It could change the size of
some while the mapping is happening on others. Have the resize of the
subbuf order check all the per_cpu buffers under the lock to see if
any of them is disabled before starting and causing an inconsistency
between buffers that are being mapped.
* tag 'trace-v7.3-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: (25 commits)
ring-buffer: Check resize_disabled before publishing the new subbuf order
tracing/remotes: Catch nr_page_va overflow in ring_buffer_desc sizing
tracing/remotes: Account for ring buffer page header in size calculation
tracing: Don't dereference trace_event_file in deferred trigger free
ftrace: Use rcu_assign_pointer() for tmp_ops filter hash
ring-buffer: Acquire the lock with irqsave in rb_wake_up_waiters()
tracing: Take trace_array reference when opening a tracer options file
tracing: Fix ring_buffer_read_page_size() kernel-doc
tracing: Restore :mod: trailer after parsing in ftrace_set_clr_event()
tracing: Fix memory corruption from a "STACKTRACE" histogram key
tracing: Fix memory corruption from the histogram stacktrace modifier
tracing: Undo the registration when enabling the histogram trigger fails
tracing: Take the reference before publishing the named histogram trigger
tracing: Set the trace clock before registering the histogram trigger
tracing: Fix typo "preceeded" in comment
tracing: Fix typo "availabe" in comment
tracing: Let histogram values keep the percent and graph modifiers
tracing: Keep the entry count when the histogram stats allocation fails
tracing: Free histogram the field rejected for a bad modifier
tracing: Free histogram the var ref when its initialization fails
...
Diffstat (limited to 'drivers/phy/spacemit')
| -rw-r--r-- | drivers/phy/spacemit/Kconfig | 41 | ||||
| -rw-r--r-- | drivers/phy/spacemit/Makefile | 5 | ||||
| -rw-r--r-- | drivers/phy/spacemit/phy-k1-pcie.c | 670 | ||||
| -rw-r--r-- | drivers/phy/spacemit/phy-k1-usb2.c | 237 | ||||
| -rw-r--r-- | drivers/phy/spacemit/phy-k3-combo.c | 252 | ||||
| -rw-r--r-- | drivers/phy/spacemit/phy-k3-common.c | 391 | ||||
| -rw-r--r-- | drivers/phy/spacemit/phy-k3-common.h | 27 |
7 files changed, 1623 insertions, 0 deletions
diff --git a/drivers/phy/spacemit/Kconfig b/drivers/phy/spacemit/Kconfig new file mode 100644 index 000000000..5fdf18fce --- /dev/null +++ b/drivers/phy/spacemit/Kconfig @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Phy drivers for SpacemiT platforms +# +config PHY_SPACEMIT_K1_PCIE + tristate "PCIe and combo PHY driver for the SpacemiT K1 SoC" + depends on ARCH_SPACEMIT || COMPILE_TEST + depends on COMMON_CLK + depends on HAS_IOMEM + depends on OF + select GENERIC_PHY + default ARCH_SPACEMIT + help + Enable support for the PCIe and USB 3 combo PHY and two + PCIe-only PHYs used in the SpacemiT K1 SoC. + +config PHY_SPACEMIT_K1_USB2 + tristate "SpacemiT K1 USB 2.0 PHY support" + depends on (ARCH_SPACEMIT || COMPILE_TEST) && OF + depends on COMMON_CLK + depends on USB_COMMON + select GENERIC_PHY + help + Enable this to support K1 USB 2.0 PHY driver. This driver takes care of + enabling and clock setup and will be used by K1 udc/ehci/otg/xhci driver. + +config PHY_SPACEMIT_K3_COMMON_OPS + tristate + select MFD_SYSCON + select GENERIC_PHY + +config PHY_SPACEMIT_K3_COMBO_PHY + tristate "SpacemiT K3 USB3/PCIe PHY support" + depends on (ARCH_SPACEMIT || COMPILE_TEST) && OF + depends on COMMON_CLK + select PHY_SPACEMIT_K3_COMMON_OPS + help + Enable this to support K3 USB3/PCIe combo PHY driver. This + driver takes care of enabling and clock setup and will be used + by K3 dwc3 driver. + If unsure, say N. diff --git a/drivers/phy/spacemit/Makefile b/drivers/phy/spacemit/Makefile new file mode 100644 index 000000000..f679c1851 --- /dev/null +++ b/drivers/phy/spacemit/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_PHY_SPACEMIT_K1_PCIE) += phy-k1-pcie.o +obj-$(CONFIG_PHY_SPACEMIT_K1_USB2) += phy-k1-usb2.o +obj-$(CONFIG_PHY_SPACEMIT_K3_COMBO_PHY) += phy-k3-combo.o +obj-$(CONFIG_PHY_SPACEMIT_K3_COMMON_OPS) += phy-k3-common.o diff --git a/drivers/phy/spacemit/phy-k1-pcie.c b/drivers/phy/spacemit/phy-k1-pcie.c new file mode 100644 index 000000000..75477bea7 --- /dev/null +++ b/drivers/phy/spacemit/phy-k1-pcie.c @@ -0,0 +1,670 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SpacemiT K1 PCIe and PCIe/USB 3 combo PHY driver + * + * Copyright (C) 2025 by RISCstar Solutions Corporation. All rights reserved. + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/iopoll.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/reset.h> + +#include <dt-bindings/phy/phy.h> + +/* + * Three PCIe ports are supported in the SpacemiT K1 SoC, and this driver + * supports their PHYs. + * + * The PHY for PCIe port A is different from the PHYs for ports B and C: + * - It has one lane, while ports B and C have two + * - It is a combo PHY can be used for PCIe or USB 3 + * - It can automatically calibrate PCIe TX and RX termination settings + * + * The PHY functionality for PCIe ports B and C is identical: + * - They have two PCIe lanes (but can be restricted to 1 via device tree) + * - They are used for PCIe only + * - They are configured using TX and RX values computed for port A + * + * A given board is designed to use the combo PHY for either PCIe or USB 3. + * Whether the combo PHY is configured for PCIe or USB 3 is specified in + * device tree using a phandle plus an argument. The argument indicates + * the type (either PHY_TYPE_PCIE or PHY_TYPE_USB3). + * + * Each PHY has a reset that it gets and deasserts during initialization. + * Each depends also on other clocks and resets provided by the controller + * hardware (PCIe or USB) it is associated with. The controller drivers + * are required to enable any clocks and de-assert any resets that affect + * PHY operation. In addition each PHY implements an internal PLL, driven + * by an external (24 MHz) oscillator. + * + * PCIe PHYs must be programmed with RX and TX calibration values. The + * combo PHY is the only one that can determine these values. They are + * determined by temporarily enabling the combo PHY in PCIe mode at probe + * time (if necessary). This calibration only needs to be done once, and + * when it has completed the TX and RX values are saved. + * + * To allow the combo PHY to be enabled for calibration, the resets and + * clocks it uses in PCIe mode must be supplied. + */ + +struct k1_pcie_phy { + struct device *dev; /* PHY provider device */ + struct phy *phy; + void __iomem *regs; + u32 pcie_lanes; /* Max (1 or 2) unless limited by DT */ + struct clk *pll; + struct clk_hw pll_hw; /* Private PLL clock */ + + /* The remaining fields are only used for the combo PHY */ + u32 type; /* PHY_TYPE_PCIE or PHY_TYPE_USB3 */ + struct regmap *pmu; /* MMIO regmap (no errors) */ +}; + +#define CALIBRATION_TIMEOUT 500000 /* For combo PHY (usec) */ +#define PLL_TIMEOUT 500000 /* For PHY PLL lock (usec) */ +#define POLL_DELAY 500 /* Time between polls (usec) */ + +/* Selecting the combo PHY operating mode requires APMU regmap access */ +#define SYSCON_APMU "spacemit,apmu" + +/* PMU space, for selecting between PCIe and USB 3 mode (combo PHY only) */ + +#define PMUA_USB_PHY_CTRL0 0x0110 +#define COMBO_PHY_SEL BIT(3) /* 0: PCIe; 1: USB 3 */ + +#define PCIE_CLK_RES_CTRL 0x03cc +#define PCIE_APP_HOLD_PHY_RST BIT(30) + +/* PHY register space */ + +/* Offset between lane 0 and lane 1 registers when there are two */ +#define PHY_LANE_OFFSET 0x0400 + +/* PHY PLL configuration */ +#define PCIE_PU_ADDR_CLK_CFG 0x0008 +#define PLL_READY BIT(0) /* read-only */ +#define CFG_INTERNAL_TIMER_ADJ GENMASK(10, 7) +#define TIMER_ADJ_USB 0x2 +#define TIMER_ADJ_PCIE 0x6 +#define CFG_SW_PHY_INIT_DONE BIT(11) /* We set after PLL config */ + +#define PCIE_RC_DONE_STATUS 0x0018 +#define CFG_FORCE_RCV_RETRY BIT(10) /* Used for PCIe */ + +/* PCIe PHY lane calibration; assumes 24MHz input clock */ +#define PCIE_RC_CAL_REG2 0x0020 +#define RC_CAL_TOGGLE BIT(22) +#define CLKSEL GENMASK(31, 29) +#define CLKSEL_24M 0x3 + +/* Additional PHY PLL configuration (USB 3 and PCIe) */ +#define PCIE_PU_PLL_1 0x0048 +#define REF_100_WSSC BIT(12) /* 1: input is 100MHz, SSC */ +#define FREF_SEL GENMASK(15, 13) +#define FREF_24M 0x1 +#define SSC_DEP_SEL GENMASK(19, 16) +#define SSC_DEP_NONE 0x0 +#define SSC_DEP_5000PPM 0xa + +/* PCIe PHY configuration */ +#define PCIE_PU_PLL_2 0x004c +#define GEN_REF100 BIT(4) /* 1: generate 100MHz clk */ + +#define PCIE_RX_REG1 0x0050 +#define EN_RTERM BIT(3) +#define AFE_RTERM_REG GENMASK(11, 8) + +#define PCIE_RX_REG2 0x0054 +#define RX_RTERM_SEL BIT(5) /* 0: use AFE_RTERM_REG value */ + +#define PCIE_LTSSM_DIS_ENTRY 0x005c +#define CFG_REFCLK_MODE GENMASK(9, 8) +#define RFCLK_MODE_DRIVER 0x1 +#define OVRD_REFCLK_MODE BIT(10) /* 1: use CFG_RFCLK_MODE */ + +#define PCIE_TX_REG1 0x0064 +#define TX_RTERM_REG GENMASK(15, 12) +#define TX_RTERM_SEL BIT(25) /* 1: use TX_RTERM_REG */ + +/* Zeroed for the combo PHY operating in USB mode */ +#define USB3_TEST_CTRL 0x0068 + +/* PHY calibration values, determined by the combo PHY at probe time */ +#define PCIE_RCAL_RESULT 0x0084 /* Port A PHY only */ +#define RTERM_VALUE_RX GENMASK(3, 0) +#define RTERM_VALUE_TX GENMASK(7, 4) +#define R_TUNE_DONE BIT(10) + +static u32 k1_phy_rterm = ~0; /* Invalid initial value */ + +/* Save the RX and TX receiver termination values */ +static void k1_phy_rterm_set(u32 val) +{ + k1_phy_rterm = val & (RTERM_VALUE_RX | RTERM_VALUE_TX); +} + +static bool k1_phy_rterm_valid(void) +{ + /* Valid if no bits outside those we care about are set */ + return !(k1_phy_rterm & ~(RTERM_VALUE_RX | RTERM_VALUE_TX)); +} + +static u32 k1_phy_rterm_rx(void) +{ + return FIELD_GET(RTERM_VALUE_RX, k1_phy_rterm); +} + +static u32 k1_phy_rterm_tx(void) +{ + return FIELD_GET(RTERM_VALUE_TX, k1_phy_rterm); +} + +/* Only the combo PHY has a PMU pointer defined */ +static bool k1_phy_port_a(struct k1_pcie_phy *k1_phy) +{ + return !!k1_phy->pmu; +} + +/* The PLL clocks are driven by the external oscillator */ +static const struct clk_parent_data k1_pcie_phy_data[] = { + { .fw_name = "refclk", }, +}; + +static struct k1_pcie_phy *clk_hw_to_k1_phy(struct clk_hw *clk_hw) +{ + return container_of(clk_hw, struct k1_pcie_phy, pll_hw); +} + +/* USB mode only works on the combo PHY, which has only one lane */ +static void k1_pcie_phy_pll_prepare_usb(struct k1_pcie_phy *k1_phy) +{ + void __iomem *regs = k1_phy->regs; + u32 val; + + val = readl(regs + PCIE_PU_ADDR_CLK_CFG); + val &= ~CFG_INTERNAL_TIMER_ADJ; + val |= FIELD_PREP(CFG_INTERNAL_TIMER_ADJ, TIMER_ADJ_USB); + writel(val, regs + PCIE_PU_ADDR_CLK_CFG); + + val = readl(regs + PCIE_PU_PLL_1); + val &= ~SSC_DEP_SEL; + val |= FIELD_PREP(SSC_DEP_SEL, SSC_DEP_5000PPM); + writel(val, regs + PCIE_PU_PLL_1); +} + +/* Perform PCIe-specific register updates before starting the PLL clock */ +static void k1_pcie_phy_pll_prepare_pcie(struct k1_pcie_phy *k1_phy) +{ + void __iomem *regs = k1_phy->regs; + u32 val; + u32 i; + + for (i = 0; i < k1_phy->pcie_lanes; i++) { + val = readl(regs + PCIE_PU_ADDR_CLK_CFG); + val &= ~CFG_INTERNAL_TIMER_ADJ; + val |= FIELD_PREP(CFG_INTERNAL_TIMER_ADJ, TIMER_ADJ_PCIE); + writel(val, regs + PCIE_PU_ADDR_CLK_CFG); + + regs += PHY_LANE_OFFSET; /* Next lane */ + } + + regs = k1_phy->regs; + val = readl(regs + PCIE_RC_DONE_STATUS); + val |= CFG_FORCE_RCV_RETRY; + writel(val, regs + PCIE_RC_DONE_STATUS); + + val = readl(regs + PCIE_PU_PLL_1); + val &= ~SSC_DEP_SEL; + val |= FIELD_PREP(SSC_DEP_SEL, SSC_DEP_NONE); + writel(val, regs + PCIE_PU_PLL_1); + + val = readl(regs + PCIE_PU_PLL_2); + val |= GEN_REF100; /* Enable 100 MHz PLL output clock */ + writel(val, regs + PCIE_PU_PLL_2); +} + +static int k1_pcie_phy_pll_prepare(struct clk_hw *clk_hw) +{ + struct k1_pcie_phy *k1_phy = clk_hw_to_k1_phy(clk_hw); + void __iomem *regs = k1_phy->regs; + u32 val; + u32 i; + + if (k1_phy_port_a(k1_phy) && k1_phy->type == PHY_TYPE_USB3) + k1_pcie_phy_pll_prepare_usb(k1_phy); + else + k1_pcie_phy_pll_prepare_pcie(k1_phy); + + /* + * Disable 100 MHz input reference with spread-spectrum + * clocking and select the 24 MHz clock input frequency + */ + val = readl(regs + PCIE_PU_PLL_1); + val &= ~REF_100_WSSC; + val &= ~FREF_SEL; + val |= FIELD_PREP(FREF_SEL, FREF_24M); + writel(val, regs + PCIE_PU_PLL_1); + + /* Mark PLL configuration done on all lanes */ + for (i = 0; i < k1_phy->pcie_lanes; i++) { + val = readl(regs + PCIE_PU_ADDR_CLK_CFG); + val |= CFG_SW_PHY_INIT_DONE; + writel(val, regs + PCIE_PU_ADDR_CLK_CFG); + + regs += PHY_LANE_OFFSET; /* Next lane */ + } + + /* + * Wait for indication the PHY PLL is locked. Lanes for ports + * B and C share a PLL, so it's enough to sample just lane 0. + */ + return readl_poll_timeout(k1_phy->regs + PCIE_PU_ADDR_CLK_CFG, + val, val & PLL_READY, + POLL_DELAY, PLL_TIMEOUT); +} + +/* Prepare implies enable, and once enabled, it's always on */ +static const struct clk_ops k1_pcie_phy_pll_ops = { + .prepare = k1_pcie_phy_pll_prepare, +}; + +/* We represent the PHY PLL as a private clock */ +static int k1_pcie_phy_pll_setup(struct k1_pcie_phy *k1_phy) +{ + struct clk_hw *hw = &k1_phy->pll_hw; + struct device *dev = k1_phy->dev; + struct clk_init_data init = { }; + char *name; + int ret; + + name = kasprintf(GFP_KERNEL, "pcie%u_phy_pll", k1_phy->phy->id); + if (!name) + return -ENOMEM; + + init.name = name; + init.ops = &k1_pcie_phy_pll_ops; + init.parent_data = k1_pcie_phy_data; + init.num_parents = ARRAY_SIZE(k1_pcie_phy_data); + + hw->init = &init; + + ret = devm_clk_hw_register(dev, hw); + + kfree(name); /* __clk_register() duplicates the name we provide */ + + if (ret) + return ret; + + k1_phy->pll = devm_clk_hw_get_clk(dev, hw, "pll"); + if (IS_ERR(k1_phy->pll)) + return PTR_ERR(k1_phy->pll); + + return 0; +} + +/* Select PCIe or USB 3 mode for the combo PHY. */ +static void k1_combo_phy_sel(struct k1_pcie_phy *k1_phy, bool usb) +{ + struct regmap *pmu = k1_phy->pmu; + + /* Only change it if it's not already in the desired state */ + if (!regmap_test_bits(pmu, PMUA_USB_PHY_CTRL0, COMBO_PHY_SEL) == usb) + regmap_assign_bits(pmu, PMUA_USB_PHY_CTRL0, COMBO_PHY_SEL, usb); +} + +static void k1_pcie_phy_init_pcie(struct k1_pcie_phy *k1_phy) +{ + u32 rx_rterm = k1_phy_rterm_rx(); + u32 tx_rterm = k1_phy_rterm_tx(); + void __iomem *regs; + u32 val; + int i; + + /* For the combo PHY, set PHY to PCIe mode */ + if (k1_phy_port_a(k1_phy)) + k1_combo_phy_sel(k1_phy, false); + + regs = k1_phy->regs; + for (i = 0; i < k1_phy->pcie_lanes; i++) { + val = readl(regs + PCIE_RX_REG1); + + /* Set RX analog front-end receiver termination value */ + val &= ~AFE_RTERM_REG; + val |= FIELD_PREP(AFE_RTERM_REG, rx_rterm); + + /* And enable refclock receiver termination */ + val |= EN_RTERM; + writel(val, regs + PCIE_RX_REG1); + + val = readl(regs + PCIE_RX_REG2); + /* Use PCIE_RX_REG1 AFE_RTERM_REG value */ + val &= ~RX_RTERM_SEL; + writel(val, regs + PCIE_RX_REG2); + + val = readl(regs + PCIE_TX_REG1); + + /* Set TX driver termination value */ + val &= ~TX_RTERM_REG; + val |= FIELD_PREP(TX_RTERM_REG, tx_rterm); + + /* Use PCIE_TX_REG1 TX_RTERM_REG value */ + val |= TX_RTERM_SEL; + writel(val, regs + PCIE_TX_REG1); + + /* Set the input clock to 24 MHz, and clear RC_CAL_TOGGLE */ + val = readl(regs + PCIE_RC_CAL_REG2); + val &= CLKSEL; + val |= FIELD_PREP(CLKSEL, CLKSEL_24M); + val &= ~RC_CAL_TOGGLE; + writel(val, regs + PCIE_RC_CAL_REG2); + + /* Now trigger recalibration by setting RC_CAL_TOGGLE again */ + val |= RC_CAL_TOGGLE; + writel(val, regs + PCIE_RC_CAL_REG2); + + val = readl(regs + PCIE_LTSSM_DIS_ENTRY); + /* Override the reference clock; set to refclk driver mode */ + val |= OVRD_REFCLK_MODE; + val &= ~CFG_REFCLK_MODE; + val |= FIELD_PREP(CFG_REFCLK_MODE, RFCLK_MODE_DRIVER); + writel(val, regs + PCIE_LTSSM_DIS_ENTRY); + + regs += PHY_LANE_OFFSET; /* Next lane */ + } +} + +/* Only called for combo PHY */ +static void k1_pcie_phy_init_usb(struct k1_pcie_phy *k1_phy) +{ + k1_combo_phy_sel(k1_phy, true); + + /* We're not doing any testing */ + writel(0, k1_phy->regs + USB3_TEST_CTRL); +} + +static int k1_pcie_phy_init(struct phy *phy) +{ + struct k1_pcie_phy *k1_phy = phy_get_drvdata(phy); + + /* Note: port type is only valid for port A (both checks needed) */ + if (k1_phy_port_a(k1_phy) && k1_phy->type == PHY_TYPE_USB3) + k1_pcie_phy_init_usb(k1_phy); + else + k1_pcie_phy_init_pcie(k1_phy); + + + return clk_prepare_enable(k1_phy->pll); +} + +static int k1_pcie_phy_exit(struct phy *phy) +{ + struct k1_pcie_phy *k1_phy = phy_get_drvdata(phy); + + clk_disable_unprepare(k1_phy->pll); + + return 0; +} + +static const struct phy_ops k1_pcie_phy_ops = { + .init = k1_pcie_phy_init, + .exit = k1_pcie_phy_exit, + .owner = THIS_MODULE, +}; + +/* + * Get values needed for calibrating PHYs operating in PCIe mode. Only + * the combo PHY is able to do this, and its calibration values are used + * for configuring all PCIe PHYs. + * + * We always need to de-assert the "global" reset on the combo PHY, + * because the USB driver depends on it. If used for PCIe, that driver + * will (also) de-assert this, but by leaving it de-asserted for the + * combo PHY, the USB driver doesn't have to do this. Note: although + * SpacemiT refers to this as the global reset, we name the "phy" reset. + * + * In addition, we guarantee the APP_HOLD_PHY_RESET bit is clear for the + * combo PHY, so the USB driver doesn't have to manage that either. The + * PCIe driver is free to change this bit for normal operation. + * + * Calibration only needs to be done once. It's possible calibration has + * already completed (e.g., it might have happened in the boot loader, or + * -EPROBE_DEFER might result in this function being called again). So we + * check that early too, to avoid doing it more than once. + * + * Otherwise we temporarily power up the PHY using the PCIe app clocks + * and resets, wait for the hardware to indicate calibration is done, + * grab the value, then shut the PHY down again. + */ +static int k1_pcie_combo_phy_calibrate(struct k1_pcie_phy *k1_phy) +{ + struct reset_control_bulk_data resets[] = { + { .id = "dbi", }, + { .id = "mstr", }, + { .id = "slv", }, + }; + struct clk_bulk_data clocks[] = { + { .id = "dbi", }, + { .id = "mstr", }, + { .id = "slv", }, + }; + struct device *dev = k1_phy->dev; + int ret = 0; + int val; + + /* Nothing to do if we already set the receiver termination value */ + if (k1_phy_rterm_valid()) + return 0; + + /* + * We also guarantee the APP_HOLD_PHY_RESET bit is clear. We can + * leave this bit clear even if an error happens below. + */ + regmap_assign_bits(k1_phy->pmu, PCIE_CLK_RES_CTRL, + PCIE_APP_HOLD_PHY_RST, false); + + /* If the calibration already completed (e.g. by U-Boot), we're done */ + val = readl(k1_phy->regs + PCIE_RCAL_RESULT); + if (val & R_TUNE_DONE) + goto out_tune_done; + + /* Put the PHY into PCIe mode */ + k1_combo_phy_sel(k1_phy, false); + + /* Get and enable the PCIe app clocks */ + ret = clk_bulk_get(dev, ARRAY_SIZE(clocks), clocks); + if (ret < 0) + goto out_tune_done; + ret = clk_bulk_prepare_enable(ARRAY_SIZE(clocks), clocks); + if (ret) + goto out_put_clocks; + + /* Get the PCIe application resets (not the PHY reset) */ + ret = reset_control_bulk_get_shared(dev, ARRAY_SIZE(resets), resets); + if (ret) + goto out_disable_clocks; + + /* De-assert the PCIe application resets */ + ret = reset_control_bulk_deassert(ARRAY_SIZE(resets), resets); + if (ret) + goto out_put_resets; + + /* + * This is the core activity here. Wait for the hardware to + * signal that it has completed calibration/tuning. Once it + * has, the register value will contain the values we'll + * use to configure PCIe PHYs. + */ + ret = readl_poll_timeout(k1_phy->regs + PCIE_RCAL_RESULT, + val, val & R_TUNE_DONE, + POLL_DELAY, CALIBRATION_TIMEOUT); + + /* Clean up. We're done with the resets and clocks */ + reset_control_bulk_assert(ARRAY_SIZE(resets), resets); +out_put_resets: + reset_control_bulk_put(ARRAY_SIZE(resets), resets); +out_disable_clocks: + clk_bulk_disable_unprepare(ARRAY_SIZE(clocks), clocks); +out_put_clocks: + clk_bulk_put(ARRAY_SIZE(clocks), clocks); +out_tune_done: + /* If we got the value without timing out, set k1_phy_rterm */ + if (!ret) + k1_phy_rterm_set(val); + + return ret; +} + +static struct phy * +k1_pcie_combo_phy_xlate(struct device *dev, const struct of_phandle_args *args) +{ + struct k1_pcie_phy *k1_phy = dev_get_drvdata(dev); + u32 type; + + /* The argument specifying the PHY mode is required */ + if (args->args_count != 1) + return ERR_PTR(-EINVAL); + + /* We only support PCIe and USB 3 mode */ + type = args->args[0]; + if (type != PHY_TYPE_PCIE && type != PHY_TYPE_USB3) + return ERR_PTR(-EINVAL); + + /* This PHY can only be used once */ + if (k1_phy->type != PHY_NONE) + return ERR_PTR(-EBUSY); + + k1_phy->type = type; + + return k1_phy->phy; +} + +/* Use the maximum number of PCIe lanes unless limited by device tree */ +static u32 k1_pcie_num_lanes(struct k1_pcie_phy *k1_phy, bool port_a) +{ + struct device *dev = k1_phy->dev; + u32 count = 0; + u32 max; + int ret; + + ret = of_property_read_u32(dev_of_node(dev), "num-lanes", &count); + if (count == 1) + return 1; + + if (count == 2 && !port_a) + return 2; + + max = port_a ? 1 : 2; + if (ret != -EINVAL) + dev_warn(dev, "bad lane count %u for port; using %u\n", + count, max); + + return max; +} + +static int k1_pcie_combo_phy_probe(struct k1_pcie_phy *k1_phy) +{ + struct device *dev = k1_phy->dev; + struct regmap *regmap; + int ret; + + /* Setting the PHY mode requires access to the PMU regmap */ + regmap = syscon_regmap_lookup_by_phandle(dev_of_node(dev), SYSCON_APMU); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "failed to get PMU\n"); + k1_phy->pmu = regmap; + + ret = k1_pcie_combo_phy_calibrate(k1_phy); + if (ret) + return dev_err_probe(dev, ret, "calibration failed\n"); + + /* Needed by k1_pcie_combo_phy_xlate(), which also sets k1_phy->type */ + dev_set_drvdata(dev, k1_phy); + + return 0; +} + +static int k1_pcie_phy_probe(struct platform_device *pdev) +{ + struct phy *(*xlate)(struct device *dev, + const struct of_phandle_args *args); + struct device *dev = &pdev->dev; + struct reset_control *phy_reset; + struct phy_provider *provider; + struct k1_pcie_phy *k1_phy; + bool probing_port_a; + int ret; + + xlate = of_device_get_match_data(dev); + probing_port_a = xlate == k1_pcie_combo_phy_xlate; + + /* Only the combo PHY can calibrate, so it must probe first */ + if (!k1_phy_rterm_valid() && !probing_port_a) + return -EPROBE_DEFER; + + k1_phy = devm_kzalloc(dev, sizeof(*k1_phy), GFP_KERNEL); + if (!k1_phy) + return -ENOMEM; + k1_phy->dev = dev; + + k1_phy->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(k1_phy->regs)) + return dev_err_probe(dev, PTR_ERR(k1_phy->regs), + "error mapping registers\n"); + + /* De-assert the PHY (global) reset and leave it that way */ + phy_reset = devm_reset_control_get_exclusive_deasserted(dev, "phy"); + if (IS_ERR(phy_reset)) + return PTR_ERR(phy_reset); + + if (probing_port_a) { + ret = k1_pcie_combo_phy_probe(k1_phy); + if (ret) + return dev_err_probe(dev, ret, + "error probing combo phy\n"); + } + + k1_phy->pcie_lanes = k1_pcie_num_lanes(k1_phy, probing_port_a); + + k1_phy->phy = devm_phy_create(dev, NULL, &k1_pcie_phy_ops); + if (IS_ERR(k1_phy->phy)) + return dev_err_probe(dev, PTR_ERR(k1_phy->phy), + "error creating phy\n"); + phy_set_drvdata(k1_phy->phy, k1_phy); + + ret = k1_pcie_phy_pll_setup(k1_phy); + if (ret) + return dev_err_probe(dev, ret, "error initializing clock\n"); + + provider = devm_of_phy_provider_register(dev, xlate); + if (IS_ERR(provider)) + return dev_err_probe(dev, PTR_ERR(provider), + "error registering provider\n"); + return 0; +} + +static const struct of_device_id k1_pcie_phy_of_match[] = { + { .compatible = "spacemit,k1-combo-phy", k1_pcie_combo_phy_xlate, }, + { .compatible = "spacemit,k1-pcie-phy", of_phy_simple_xlate, }, + { }, +}; +MODULE_DEVICE_TABLE(of, k1_pcie_phy_of_match); + +static struct platform_driver k1_pcie_phy_driver = { + .probe = k1_pcie_phy_probe, + .driver = { + .of_match_table = k1_pcie_phy_of_match, + .name = "spacemit-k1-pcie-phy", + } +}; +module_platform_driver(k1_pcie_phy_driver); + +MODULE_DESCRIPTION("SpacemiT K1 PCIe and USB 3 PHY driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/spacemit/phy-k1-usb2.c b/drivers/phy/spacemit/phy-k1-usb2.c new file mode 100644 index 000000000..8df12eeb2 --- /dev/null +++ b/drivers/phy/spacemit/phy-k1-usb2.c @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * SpacemiT K1 USB 2.0 PHY driver + * + * Copyright (C) 2025 SpacemiT (Hangzhou) Technology Co. Ltd + * Copyright (C) 2025 Ze Huang <huang.ze@linux.dev> + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/iopoll.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/usb/of.h> + +#define PHY_RST_MODE_CTRL 0x04 +#define PHY_PLL_RDY BIT(0) +#define PHY_CLK_CDR_EN BIT(1) +#define PHY_CLK_PLL_EN BIT(2) +#define PHY_CLK_MAC_EN BIT(3) +#define PHY_MAC_RSTN BIT(5) +#define PHY_CDR_RSTN BIT(6) +#define PHY_PLL_RSTN BIT(7) +/* + * hs line state sel (Bit 13): + * - 1 (Default): Internal HS line state is set to 01 when usb_hs_tx_en is valid. + * - 0: Internal HS line state is always driven by usb_hs_lstate. + * + * fs line state sel (Bit 14): + * - 1 (Default): FS line state is determined by the output data + * (usb_fs_datain/b). + * - 0: FS line state is always determined by the input data (dmo/dpo). + */ +#define PHY_HS_LINE_TX_MODE BIT(13) +#define PHY_FS_LINE_TX_MODE BIT(14) + +#define PHY_INIT_MODE_BITS (PHY_FS_LINE_TX_MODE | PHY_HS_LINE_TX_MODE) +#define PHY_CLK_ENABLE_BITS (PHY_CLK_PLL_EN | PHY_CLK_CDR_EN | \ + PHY_CLK_MAC_EN) +#define PHY_DEASSERT_RST_BITS (PHY_PLL_RSTN | PHY_CDR_RSTN | \ + PHY_MAC_RSTN) + +#define PHY_TX_HOST_CTRL 0x10 +#define PHY_HST_DISC_AUTO_CLR BIT(2) /* autoclear hs host disc when re-connect */ + +#define PHY_HSTXP_HW_CTRL 0x34 +#define PHY_HSTXP_RSTN BIT(2) /* generate reset for clock hstxp */ +#define PHY_CLK_HSTXP_EN BIT(3) /* clock hstxp enable */ +#define PHY_HSTXP_MODE BIT(4) /* 0: force en_txp to be 1; 1: no force */ + +#define PHY_K1_HS_HOST_DISC 0x40 +#define PHY_K1_HS_HOST_DISC_CLR BIT(0) + +#define PHY_K3_HS_HOST_DISC 0x20 +#define PHY_K3_HS_HOST_DISC_CLR BIT(8) + +#define PHY_PLL_DIV_CFG 0x98 +#define PHY_FDIV_FRACT_8_15 GENMASK(7, 0) +#define PHY_FDIV_FRACT_16_19 GENMASK(11, 8) +#define PHY_FDIV_FRACT_20_21 BIT(12) /* fdiv_reg<21>, <20>, bit21 == bit20 */ +/* + * freq_sel<1:0> + * if ref clk freq=24.0MHz-->freq_sel<2:0> == 3b'001, then internal divider value == 80 + */ +#define PHY_FDIV_FRACT_0_1 GENMASK(14, 13) +/* + * pll divider value selection + * 1: divider value will choose internal default value ,dependent on freq_sel<1:0> + * 0: divider value will be over ride by fdiv_reg<21:0> + */ +#define PHY_DIV_LOCAL_EN BIT(15) + +#define PHY_SEL_FREQ_24MHZ 0x01 +#define FDIV_REG_MASK (PHY_FDIV_FRACT_20_21 | PHY_FDIV_FRACT_16_19 | \ + PHY_FDIV_FRACT_8_15) +#define FDIV_REG_VAL 0x1ec4 /* 0x100 selects 24MHz, rest are default */ + +#define K1_USB2PHY_RESET_TIME_MS 50 + +struct spacemit_usb2phy { + struct phy *phy; + struct clk *clk; + struct regmap *regmap_base; +}; + +static const struct regmap_config phy_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x200, +}; + +static int spacemit_usb2phy_init(struct phy *phy) +{ + struct spacemit_usb2phy *sphy = phy_get_drvdata(phy); + struct regmap *map = sphy->regmap_base; + u32 val; + int ret; + + ret = clk_enable(sphy->clk); + if (ret) { + dev_err(&phy->dev, "failed to enable clock\n"); + return ret; + } + + /* + * make sure the usb controller is not under reset process before + * any configuration + */ + usleep_range(150, 200); + + /* 24M ref clk */ + val = FIELD_PREP(FDIV_REG_MASK, FDIV_REG_VAL) | + FIELD_PREP(PHY_FDIV_FRACT_0_1, PHY_SEL_FREQ_24MHZ) | + PHY_DIV_LOCAL_EN; + regmap_write(map, PHY_PLL_DIV_CFG, val); + + ret = regmap_read_poll_timeout(map, PHY_RST_MODE_CTRL, val, + (val & PHY_PLL_RDY), + 500, K1_USB2PHY_RESET_TIME_MS * 1000); + if (ret) { + dev_err(&phy->dev, "wait PLLREADY timeout\n"); + clk_disable(sphy->clk); + return ret; + } + + /* release usb2 phy internal reset and enable clock gating */ + val = (PHY_INIT_MODE_BITS | PHY_CLK_ENABLE_BITS | PHY_DEASSERT_RST_BITS); + regmap_write(map, PHY_RST_MODE_CTRL, val); + + val = (PHY_HSTXP_RSTN | PHY_CLK_HSTXP_EN | PHY_HSTXP_MODE); + regmap_write(map, PHY_HSTXP_HW_CTRL, val); + + /* auto clear host disc */ + regmap_update_bits(map, PHY_TX_HOST_CTRL, PHY_HST_DISC_AUTO_CLR, + PHY_HST_DISC_AUTO_CLR); + + return 0; +} + +static int spacemit_usb2phy_exit(struct phy *phy) +{ + struct spacemit_usb2phy *sphy = phy_get_drvdata(phy); + + clk_disable(sphy->clk); + + return 0; +} + +static int spacemit_k1_usb2phy_disconnect(struct phy *phy, int port) +{ + struct spacemit_usb2phy *sphy = phy_get_drvdata(phy); + + regmap_update_bits(sphy->regmap_base, PHY_K1_HS_HOST_DISC, + PHY_K1_HS_HOST_DISC_CLR, PHY_K1_HS_HOST_DISC_CLR); + + return 0; +} + +static int spacemit_k3_usb2phy_disconnect(struct phy *phy, int port) +{ + struct spacemit_usb2phy *sphy = phy_get_drvdata(phy); + + regmap_update_bits(sphy->regmap_base, PHY_K3_HS_HOST_DISC, + PHY_K3_HS_HOST_DISC_CLR, PHY_K3_HS_HOST_DISC_CLR); + + return 0; +} + +static const struct phy_ops spacemit_k1_usb2phy_ops = { + .init = spacemit_usb2phy_init, + .exit = spacemit_usb2phy_exit, + .disconnect = spacemit_k1_usb2phy_disconnect, + .owner = THIS_MODULE, +}; + +static const struct phy_ops spacemit_k3_usb2phy_ops = { + .init = spacemit_usb2phy_init, + .exit = spacemit_usb2phy_exit, + .disconnect = spacemit_k3_usb2phy_disconnect, + .owner = THIS_MODULE, +}; + +static int spacemit_usb2phy_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + struct spacemit_usb2phy *sphy; + const struct phy_ops *ops; + void __iomem *base; + + sphy = devm_kzalloc(dev, sizeof(*sphy), GFP_KERNEL); + if (!sphy) + return -ENOMEM; + + ops = device_get_match_data(dev); + + sphy->clk = devm_clk_get_prepared(&pdev->dev, NULL); + if (IS_ERR(sphy->clk)) + return dev_err_probe(dev, PTR_ERR(sphy->clk), "Failed to get clock\n"); + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + sphy->regmap_base = devm_regmap_init_mmio(dev, base, &phy_regmap_config); + if (IS_ERR(sphy->regmap_base)) + return dev_err_probe(dev, PTR_ERR(sphy->regmap_base), "Failed to init regmap\n"); + + sphy->phy = devm_phy_create(dev, NULL, ops); + if (IS_ERR(sphy->phy)) + return dev_err_probe(dev, PTR_ERR(sphy->phy), "Failed to create phy\n"); + + phy_set_drvdata(sphy->phy, sphy); + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id spacemit_usb2phy_dt_match[] = { + { .compatible = "spacemit,k1-usb2-phy", .data = &spacemit_k1_usb2phy_ops }, + { .compatible = "spacemit,k3-usb2-phy", .data = &spacemit_k3_usb2phy_ops }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, spacemit_usb2phy_dt_match); + +static struct platform_driver spacemit_usb2_phy_driver = { + .probe = spacemit_usb2phy_probe, + .driver = { + .name = "spacemit-usb2-phy", + .of_match_table = spacemit_usb2phy_dt_match, + }, +}; +module_platform_driver(spacemit_usb2_phy_driver); + +MODULE_DESCRIPTION("Spacemit USB 2.0 PHY driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/spacemit/phy-k3-combo.c b/drivers/phy/spacemit/phy-k3-combo.c new file mode 100644 index 000000000..0bc08bb62 --- /dev/null +++ b/drivers/phy/spacemit/phy-k3-combo.c @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * phy-k3-combo.c - SpacemiT K3 PCIe/USB3 Combo PHY Driver + * + * Copyright (c) 2025 SpacemiT Technology Co. Ltd + */ + +#include <linux/bitfield.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/mfd/syscon.h> +#include <linux/iopoll.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/phy/phy.h> + +#include <dt-bindings/phy/phy.h> + +#include "phy-k3-common.h" + +/* + * The PCIE/USB Subsystem on SpacemiT K3 have 3 single lane PIPE3 PHYs + * (PHY2/3/4) shared by PCIE PortC/D and USB3 PortB/C/D. + * + * PMUA_PCIE_SUBSYS_MGMT[4:0] + * + * bit4 = 0 : PCIe A X8 mode, all 8 lanes dedicated to PCIe Port A + * 1 : PHY lanes shared between PCIe or USB according to [3:0] + * + * All PHY matrix combinations according to [4:0]: + * + * 0x0X : PCIe-A X8 + * 0x10 : PCIe-C x2 (PHY2+PHY3) + PCIe-D x1 (PHY4) + * 0x11 : PCIe-C x2 (PHY2+PHY3) + USB-D (PHY4) + * 0x12 : PCIe-C x1 (PHY2) + USB-C (PHY3) + * 0x13 : PCIe-C x1 (PHY2) + USB-C (PHY3) + USB-D (PHY4) + * 0x14 : PCIe-C x1 (PHY3) + USB-B (PHY2) + * 0x15 : PCIe-C x1 (PHY3) + USB-B (PHY2) + USB-D (PHY4) + * 0x16 : USB-B (PHY2) + USB-C (PHY3) + PCIe D x1 (PHY4) + * 0x17 : USB-B (PHY2) + USB-C (PHY3) + USB-D (PHY4) + * + * So any USB Port B/C/D operation requires PCIe A X8 mode to be disabled. + */ +#define PMUA_PCIE_SUBSYS_MGMT 0x1d8 +#define PU_MATRIX_CONF_MASK GENMASK(4, 0) + +#define COMBPHY_MAX_SUBPHYS 6 + +struct k3_combo_phy { + struct device *dev; + struct k3_lane_group groups[COMBPHY_MAX_SUBPHYS]; + void __iomem *base; + struct regmap *apb_spare; +}; + +static const struct k3_phy_lane_group_data k3_combphy_lane_group0 = { + .lanes = 2, + .config = 0xff, + .mask = 0x00, + .offsets = { + 0x0, 0x400 + }, +}; + +static const struct k3_phy_lane_group_data k3_combphy_lane_group1 = { + .lanes = 2, + .config = 0xff, + .mask = 0x00, + .offsets = { + 0x100000, 0x100400 + }, +}; + +static const struct k3_phy_lane_group_data k3_combphy_lane_group2 = { + .lanes = 1, + .config = 0x14, + .mask = 0x14, + .offsets = { + 0x200000 + }, +}; + +static const struct k3_phy_lane_group_data k3_combphy_lane_group3 = { + .lanes = 1, + .config = 0x12, + .mask = 0x12, + .offsets = { + 0x300000 + }, +}; + +static const struct k3_phy_lane_group_data k3_combphy_lane_group4 = { + .lanes = 1, + .config = 0x11, + .mask = 0x11, + .offsets = { + 0x400000 + }, +}; + +static const struct k3_phy_lane_group_data k3_combphy_lane_group5 = { + .lanes = 1, + .config = 0xff, + .mask = 0x00, + .offsets = { + 0x500000 + }, +}; + +static const struct k3_phy_lane_group_data *k3_combphy_lane_datas[] = { + &k3_combphy_lane_group0, + &k3_combphy_lane_group1, + &k3_combphy_lane_group2, + &k3_combphy_lane_group3, + &k3_combphy_lane_group4, + &k3_combphy_lane_group5, +}; + +static int k3_combo_phy_init_lanes(struct k3_combo_phy *phy, unsigned int config) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(k3_combphy_lane_datas); i++) { + const struct k3_phy_lane_group_data *data = k3_combphy_lane_datas[i]; + struct k3_lane_group *lg = &phy->groups[i]; + const struct phy_ops *ops; + bool is_usb; + + is_usb = (data->mask & config) == data->config; + if (is_usb) + ops = &k3_usb3_phy_ops; + else + ops = &k3_pcie_phy_ops; + + dev_dbg(phy->dev, "phy %d is %s\n", i, is_usb ? "usb" : "pcie"); + + lg->phy = devm_phy_create(phy->dev, NULL, ops); + if (IS_ERR(lg->phy)) + return PTR_ERR(lg->phy); + + lg->is_pcie = !is_usb; + lg->data = data; + lg->base = phy->base; + phy_set_drvdata(lg->phy, lg); + } + + return 0; +} + +static int k3_combo_phy_update_config(struct regmap *apmu, unsigned int config) +{ + if (config & ~PU_MATRIX_CONF_MASK) + return -EINVAL; + + return regmap_update_bits(apmu, PMUA_PCIE_SUBSYS_MGMT, PU_MATRIX_CONF_MASK, config); +} + +static struct phy *k3_combo_phy_xlate(struct device *dev, const struct of_phandle_args *args) +{ + struct k3_combo_phy *phy = dev_get_drvdata(dev); + struct k3_lane_group *lg; + + if (args->args_count != 2) { + dev_err(dev, "Invalid number of arguments\n"); + return ERR_PTR(-EINVAL); + } + + if (args->args[0] >= ARRAY_SIZE(k3_combphy_lane_datas)) { + dev_err(dev, "Invalid PHY id\n"); + return ERR_PTR(-EINVAL); + } + + lg = &phy->groups[args->args[0]]; + + if ((lg->is_pcie && args->args[1] != PHY_TYPE_PCIE) || + (!lg->is_pcie && args->args[1] != PHY_TYPE_USB3)) { + dev_err(dev, "Invalid PHY mode\n"); + return ERR_PTR(-EINVAL); + } + + return lg->phy; +} + +static int k3_combo_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct phy_provider *provider; + struct k3_combo_phy *phy; + struct regmap *apmu; + u32 config = 0; + int ret; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + phy->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(phy->base)) + return PTR_ERR(phy->base); + + phy->apb_spare = syscon_regmap_lookup_by_phandle(node, "spacemit,apb-spare"); + if (IS_ERR(phy->apb_spare)) + return dev_err_probe(dev, PTR_ERR(phy->apb_spare), + "Failed to find APB SPARE syscon"); + + apmu = syscon_regmap_lookup_by_phandle_args(node, "spacemit,apmu", 1, &config); + if (IS_ERR(apmu)) + return dev_err_probe(dev, PTR_ERR(apmu), + "Failed to find APMU syscon"); + + ret = k3_combo_phy_update_config(apmu, config); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to set lane configuration"); + + phy->dev = dev; + platform_set_drvdata(pdev, phy); + + ret = k3_phy_calibrate(phy->apb_spare); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to calibrate phy"); + + ret = k3_combo_phy_init_lanes(phy, config); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to init lanes"); + + provider = devm_of_phy_provider_register(dev, k3_combo_phy_xlate); + if (IS_ERR(provider)) + return dev_err_probe(dev, PTR_ERR(provider), + "Failed to register provider\n"); + + return 0; +} + +static const struct of_device_id k3_combo_phy_of_match[] = { + { .compatible = "spacemit,k3-combo-phy" }, + { }, +}; +MODULE_DEVICE_TABLE(of, k3_combo_phy_of_match); + +static struct platform_driver k3_combo_phy_driver = { + .probe = k3_combo_phy_probe, + .driver = { + .name = "spacemit,k3-combo-phy", + .of_match_table = k3_combo_phy_of_match, + }, +}; +module_platform_driver(k3_combo_phy_driver); + +MODULE_DESCRIPTION("SpacemiT K3 USB3/PCIe combo PHY driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/spacemit/phy-k3-common.c b/drivers/phy/spacemit/phy-k3-common.c new file mode 100644 index 000000000..0b289e63d --- /dev/null +++ b/drivers/phy/spacemit/phy-k3-common.c @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <linux/bitfield.h> +#include <linux/cleanup.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/usb.h> + +#include <dt-bindings/phy/phy.h> + +#include "phy-k3-common.h" + +/* PHY Registers */ +#define PHY_VERSION 0x0 + +#define PHY_RESET_CFG 0x04 + +#define PHY_RESET_RXBUF_RST BIT(0) +#define PHY_RESET_SOFT_RST_PCS BIT(1) +#define PHY_RESET_SOFT_RST_AHB BIT(2) +#define PHY_RESET_EN_SD_AFTER_LOCK BIT(6) + +#define PHY_CLK_CFG 0x08 + +#define PHY_CLK_PLL_READY BIT(0) +#define PHY_CLK_TXCLK_INV BIT(2) +#define PHY_CLK_RXCLK_EN BIT(3) +#define PHY_CLK_TXCLK_EN BIT(4) +#define PHY_CLK_PCLK_EN BIT(5) +#define PHY_CLK_PIPE_PCLK_EN BIT(6) +#define PHY_CLK_REFCLK_FREQ GENMASK(10, 7) +#define PHY_CLK_REFCLK_24M 2 +#define PHY_CLK_SW_INIT_DONE BIT(11) +#define PHY_CLK_PU_SSC_OUT BIT(23) + +#define PHY_MODE_CFG 0x0C + +#define PHY_MODE_PCIE_INT_EN BIT(0) +#define PHY_MODE_LFPS_TPERIOD GENMASK(9, 8) +#define PHY_MODE_LFPS_TPERIOD_USB 3 + +#define PHY_PU_SEL 0x40 + +#define PHY_PU_CFG_STATUS BIT(9) +#define PHY_PU_OVRD_STATUS BIT(10) + +#define PHY_PU_CK_REG 0x54 + +#define PHY_PU_REFCLK_100 BIT(25) + +#define PHY_PLL_REG1 0x58 + +#define PHY_PLL_FREF_SEL GENMASK(15, 13) +#define PHY_PLL_FREF_24M 0x1 +#define PHY_PLL_SSC_DEP_SEL GENMASK(27, 24) +#define PHY_PLL_SSC_5000PPM 0xa +#define PHY_PLL_SSC_MODE GENMASK(29, 28) +#define PHY_PLL_SSC_MODE_CENTER_SPREAD 0 +#define PHY_PLL_SSC_MODE_UP_SPREAD 1 +#define PHY_PLL_SSC_MODE_DOWN_SPREAD 2 +#define PHY_PLL_SSC_MODE_DOWN_SPREAD1 3 + +#define PHY_PLL_REG2 0x5c + +#define PHY_PLL_SEL_REF100 BIT(21) + +/* PHY RX Register Definitions */ +#define PHY_RX_REG_A 0x60 + +#define PHY_RX_REG0_MASK GENMASK(7, 0) +#define PHY_RX_REG1_MASK GENMASK(15, 8) +#define PHY_RX_REG2_MASK GENMASK(23, 16) +#define PHY_RX_REG3_MASK GENMASK(31, 24) + +#define PHY_RX_REG0_RLOAD BIT(4) +#define PHY_RX_REG1_RTERM GENMASK(11, 8) +#define PHY_RX_REG1_RC_CALI GENMASK(15, 12) +#define PHY_RX_REG2_CSEL GENMASK(19, 16) +#define PHY_RX_REG2_FORCE_CSEL BIT(20) +#define PHY_RX_REG2_PSEL GENMASK(23, 21) +#define PHY_RX_REG3_I_LOAD GENMASK(26, 24) +#define PHY_RX_REG3_SEL_CBOOST_CODE BIT(27) +#define PHY_RX_REG3_ADJ_BIAS GENMASK(29, 28) +#define PHY_RX_REG3_RDEG1 GENMASK(31, 30) + +#define PHY_RX_REG_B 0x64 + +#define PHY_RX_REG4_MASK GENMASK(7, 0) +#define PHY_RX_REG5_MASK GENMASK(15, 8) +#define PHY_RX_REG6_MASK GENMASK(23, 16) + +#define PHY_RX_REGB_MASK GENMASK(23, 0) + +#define PHY_RX_REG4_RDEG2 GENMASK(2, 1) +#define PHY_RX_REG4_ENVOS BIT(4) +#define PHY_RX_REG4_RTERM_SEL BIT(5) +#define PHY_RX_REG4_MANUAL_CFG BIT(7) +#define PHY_RX_REG5_RCELL_VCM GENMASK(11, 8) +#define PHY_RX_REG5_RCELL_BIAS GENMASK(15, 12) +#define PHY_RX_REG6_H1_REG GENMASK(19, 16) +#define PHY_RX_REG6_ADAPT_GAIN GENMASK(21, 20) +#define PHY_RX_REG6_BYPASS_ADPT BIT(22) + +#define PHY_ADPT_CFG0 0x140 +#define PHY_ADPT_AFE_RST_OVRD_EN BIT(1) +#define PHY_ADPT_AFE_RST_OVRD_VAL BIT(4) + +#define PHY_RXEQ_TIME 0xb4 +#define PHY_RXEQ_TIME_OVRD_POST_C_SOC BIT(21) +#define PHY_RXEQ_TIME_CFG_AMP_SOC GENMASK(23, 22) +#define PHY_RXEQ_TIME_AMP_SOC_650M 0 +#define PHY_RXEQ_TIME_AMP_SOC_800M 1 +#define PHY_RXEQ_TIME_AMP_SOC_870M 2 +#define PHY_RXEQ_TIME_AMP_SOC_900M 3 +#define PHY_RXEQ_TIME_OVRD_AMP_SOC BIT(24) + +#define PCIE_PU_ADDR_CLK_CFG 0x0008 +#define PHY_CLK_PLL_READY BIT(0) +#define PCIE_INITAL_TIMER GENMASK(6, 3) +#define CFG_INTERNAL_TIMER_ADJ GENMASK(10, 7) +#define CFG_SW_PHY_INIT_DONE BIT(11) + +/* Lane RX/TX configuration (per‑lane, at lane_base) */ +#define PCIE_RX_REG1 0x050 +#define PCIE_RX_REFCLK_MODE GENMASK(1, 0) +#define PCIE_RX_REFCLK_MODE_DRIVER 1 +#define PCIE_RX_SEL_TRI_CODE BIT(2) +#define PCIE_RX_LEGACY GENMASK(15, 8) +#define PCIE_RX_LEGACY_DEFAULT 0x65 + +#define PCIE_TX_REG1 0x064 + +#define PCIE_PLL_TIMEOUT 500000 +#define PCIE_POLL_DELAY 500 + +static int k3_usb3phy_init_single(struct k3_lane_group *lg, void __iomem *base) +{ + struct phy *phy = lg->phy; + u32 val, tmp; + int ret; + + /* Do not wait CDR lock before sampling data */ + val = readl(base + PHY_RESET_CFG); + val = u32_replace_bits(val, 0, PHY_RESET_EN_SD_AFTER_LOCK); + writel(val, base + PHY_RESET_CFG); + + /* Power down 100MHz refclk buffer */ + val = readl(base + PHY_PU_CK_REG); + val = u32_replace_bits(val, 0, PHY_PU_REFCLK_100); + writel(val, base + PHY_PU_CK_REG); + + /* Program PLL REG1 configure the SSC */ + val = FIELD_PREP(PHY_PLL_SSC_MODE, PHY_PLL_SSC_MODE_DOWN_SPREAD1) | + FIELD_PREP(PHY_PLL_SSC_DEP_SEL, PHY_PLL_SSC_5000PPM) | + FIELD_PREP(PHY_PLL_FREF_SEL, PHY_PLL_FREF_24M); + writel(val, base + PHY_PLL_REG1); + + /* Un-select 100MHz PLL reference */ + val = readl(base + PHY_PLL_REG2); + val = u32_replace_bits(val, 0, PHY_PLL_SEL_REF100); + writel(val, base + PHY_PLL_REG2); + + /* USB LFPS period configuration */ + val = readl(base + PHY_MODE_CFG); + val = u32_replace_bits(val, PHY_MODE_LFPS_TPERIOD_USB, PHY_MODE_LFPS_TPERIOD); + writel(val, base + PHY_MODE_CFG); + + /* Force AFE adaptation reset */ + val = readl(base + PHY_ADPT_CFG0); + val |= PHY_ADPT_AFE_RST_OVRD_EN | PHY_ADPT_AFE_RST_OVRD_VAL; + writel(val, base + PHY_ADPT_CFG0); + + /* Override driver amplitude value to 900m */ + val = readl(base + PHY_RXEQ_TIME); + val |= PHY_RXEQ_TIME_OVRD_AMP_SOC; + val = u32_replace_bits(val, PHY_RXEQ_TIME_AMP_SOC_900M, PHY_RXEQ_TIME_CFG_AMP_SOC); + writel(val, base + PHY_RXEQ_TIME); + + /* Configure RX parameters */ + val = PHY_RX_REG0_RLOAD | + FIELD_PREP(PHY_RX_REG1_RTERM, 0x8) | + FIELD_PREP(PHY_RX_REG1_RC_CALI, 0x7) | + FIELD_PREP(PHY_RX_REG2_CSEL, 0x8) | + PHY_RX_REG2_FORCE_CSEL | + FIELD_PREP(PHY_RX_REG2_PSEL, 0x4) | + FIELD_PREP(PHY_RX_REG3_I_LOAD, 0x7) | + PHY_RX_REG3_SEL_CBOOST_CODE | + FIELD_PREP(PHY_RX_REG3_ADJ_BIAS, 0x1) | + FIELD_PREP(PHY_RX_REG3_RDEG1, 0x3); + writel(val, base + PHY_RX_REG_A); + + val = readl(base + PHY_RX_REG_B); + tmp = FIELD_PREP(PHY_RX_REG4_RDEG2, 0x2) | + PHY_RX_REG4_ENVOS | PHY_RX_REG4_RTERM_SEL | PHY_RX_REG4_MANUAL_CFG | + FIELD_PREP(PHY_RX_REG5_RCELL_VCM, 0x8) | + FIELD_PREP(PHY_RX_REG5_RCELL_BIAS, 0x8) | + FIELD_PREP(PHY_RX_REG6_H1_REG, 0x8) | + FIELD_PREP(PHY_RX_REG6_ADAPT_GAIN, 0x2); + val = u32_replace_bits(val, tmp, PHY_RX_REGB_MASK); + writel(val, base + PHY_RX_REG_B); + + /* + * Inform PHY that all PLL-related configuration is done. + * PLL will not start locking until PHY_CLK_SW_INIT_DONE is set. + */ + val = PHY_CLK_SW_INIT_DONE | PHY_CLK_PU_SSC_OUT | + FIELD_PREP(PHY_CLK_REFCLK_FREQ, PHY_CLK_REFCLK_24M) | + PHY_CLK_RXCLK_EN | PHY_CLK_TXCLK_EN | + PHY_CLK_PCLK_EN | PHY_CLK_PIPE_PCLK_EN; + writel(val, base + PHY_CLK_CFG); + + ret = readl_poll_timeout(base + PHY_CLK_CFG, val, + (val & PHY_CLK_PLL_READY), + PCIE_POLL_DELAY, PCIE_PLL_TIMEOUT); + if (ret) { + dev_err(&phy->dev, "PHY PLL polling timeout\n"); + return ret; + } + + return 0; +} + +static int k3_usb3phy_init(struct phy *phy) +{ + struct k3_lane_group *lg = phy_get_drvdata(phy); + int ret, i; + + for (i = 0; i < lg->data->lanes; i++) { + ret = k3_usb3phy_init_single(lg, lg->base + lg->data->offsets[i]); + if (ret < 0) + return ret; + } + + return 0; +} + +const struct phy_ops k3_usb3_phy_ops = { + .init = k3_usb3phy_init, + .owner = THIS_MODULE, +}; +EXPORT_SYMBOL_GPL(k3_usb3_phy_ops); + +static int k3_pcie_phy_init(struct phy *phy) +{ + struct k3_lane_group *lg = phy_get_drvdata(phy); + void __iomem *phy_base = lg->base + lg->data->offsets[0]; + u32 val; + int ret; + int i; + + val = readl(phy_base + PHY_PLL_REG1); + val = u32_replace_bits(val, 0x2, GENMASK(15, 12)); + writel(val, phy_base + PHY_PLL_REG1); + + val = readl(phy_base + PHY_PLL_REG2); + val = u32_replace_bits(val, 0, BIT(21)); + writel(val, phy_base + PHY_PLL_REG2); + + for (i = 0; i < lg->data->lanes; i++) { + void __iomem *lane_base = lg->base + lg->data->offsets[i]; + + val = readl(lane_base + PCIE_RX_REG1); + val = u32_replace_bits(val, 0, 0x3); + writel(val, lane_base + PCIE_RX_REG1); + } + + val = readl(phy_base + PHY_PLL_REG2); + val |= BIT(20); + writel(val, phy_base + PHY_PLL_REG2); + + /* The write is needed as clock requires renegotiation */ + val = FIELD_PREP(PCIE_RX_REFCLK_MODE, PCIE_RX_REFCLK_MODE_DRIVER) | + PCIE_RX_SEL_TRI_CODE | + FIELD_PREP(PCIE_RX_LEGACY, PCIE_RX_LEGACY_DEFAULT); + writel(val, phy_base + PCIE_RX_REG1); + + /* pll_reg1 of lane0, disable SSC: pll[27:24] = 0 */ + val = readl(phy_base + PHY_PLL_REG1); + val = u32_replace_bits(val, 0, GENMASK(27, 24)); + writel(val, phy_base + PHY_PLL_REG1); + + for (i = 0; i < lg->data->lanes; i++) { + void __iomem *lane_base = lg->base + lg->data->offsets[i]; + + /* set cfg_tx_send_dummy_data to be 1'b1 for disable dash data */ + val = readl(lane_base + PHY_PU_SEL); + val = u32_replace_bits(val, 1, BIT(13)); + writel(val, lane_base + PHY_PU_SEL); + + /* disable en_sample_data_after_cdr_locked */ + val = readl(lane_base + PHY_RESET_CFG); + val = u32_replace_bits(val, 0, BIT(6)); + writel(val, lane_base + PHY_RESET_CFG); + + /* Dynamic Lock */ + val = readl(lane_base + PHY_MODE_CFG); + val = u32_replace_bits(val, 1, BIT(2)); + writel(val, lane_base + PHY_MODE_CFG); + + val = FIELD_PREP(PHY_RX_REG0_MASK, 0x10) | + FIELD_PREP(PHY_RX_REG1_MASK, 0x78) | + FIELD_PREP(PHY_RX_REG2_MASK, 0x98) | + FIELD_PREP(PHY_RX_REG3_MASK, 0xdf); + writel(val, lane_base + PHY_RX_REG_A); + + val = readl(lane_base + PHY_RX_REG_B); + val &= ~PHY_RX_REGB_MASK; + val |= FIELD_PREP(PHY_RX_REG4_MASK, 0xb4) | + FIELD_PREP(PHY_RX_REG5_MASK, 0x88) | + FIELD_PREP(PHY_RX_REG6_MASK, 0x28); + writel(val, lane_base + PHY_RX_REG_B); + + /* Set init done */ + val = readl(lane_base + PCIE_PU_ADDR_CLK_CFG); + val = u32_replace_bits(val, 1, CFG_SW_PHY_INIT_DONE); + writel(val, lane_base + PCIE_PU_ADDR_CLK_CFG); + } + + ret = readl_poll_timeout(phy_base + PCIE_PU_ADDR_CLK_CFG, val, + (val & PHY_CLK_PLL_READY), PCIE_POLL_DELAY, + PCIE_PLL_TIMEOUT); + if (ret) { + dev_err(&lg->phy->dev, "PHY PLL lock timeout\n"); + return ret; + } + + return 0; +} + +const struct phy_ops k3_pcie_phy_ops = { + .init = k3_pcie_phy_init, + .owner = THIS_MODULE, +}; +EXPORT_SYMBOL_GPL(k3_pcie_phy_ops); + +/* PHY rcal init requires APB_SPARE regmap access */ + +#define APB_SPARE_PU_CAL 0x178 +#define PU_CAL BIT(17) + +#define APB_SPARE_RCAL_HSIO 0x17c +#define APB_SPARE_PU_CAL_DONE BIT(8) +#define RCAL_OVRD_PTRIM GENMASK(23, 20) +#define RCAL_OVRD_NTRIM GENMASK(27, 24) +#define RCAL_OVRD_PTRIM_EN BIT(28) +#define RCAL_OVRD_NTRIM_EN BIT(29) +#define RCAL_OVRD_STABLE_VAL BIT(30) +#define RCAL_OVRD_STABLE_EN BIT(31) + +#define RCAL_OVRD_TRIM_EN (RCAL_OVRD_NTRIM_EN | RCAL_OVRD_PTRIM_EN) +#define RCAL_OVRD_TRIM_MASK (RCAL_OVRD_NTRIM | RCAL_OVRD_PTRIM) + +#define PU_CAL_TIMEOUT 2000000 + +static DEFINE_MUTEX(calibrate_lock); + +int k3_phy_calibrate(struct regmap *apb_spare) +{ + unsigned int val = 0; + int ret; + + guard(mutex)(&calibrate_lock); + + regmap_read(apb_spare, APB_SPARE_RCAL_HSIO, &val); + if (val & APB_SPARE_PU_CAL_DONE) + return 0; + + regmap_update_bits(apb_spare, APB_SPARE_PU_CAL, PU_CAL, + PU_CAL); + + ret = regmap_read_poll_timeout(apb_spare, APB_SPARE_RCAL_HSIO, + val, (val & APB_SPARE_PU_CAL_DONE), PCIE_POLL_DELAY, + PU_CAL_TIMEOUT); + + if (ret) + regmap_update_bits(apb_spare, APB_SPARE_RCAL_HSIO, + RCAL_OVRD_TRIM_EN | RCAL_OVRD_STABLE_VAL | + RCAL_OVRD_TRIM_MASK | RCAL_OVRD_STABLE_EN, + RCAL_OVRD_TRIM_EN | RCAL_OVRD_STABLE_VAL | + FIELD_PREP(RCAL_OVRD_NTRIM, 0x6) | + FIELD_PREP(RCAL_OVRD_PTRIM, 0xa) | + RCAL_OVRD_STABLE_EN); + + return 0; +} +EXPORT_SYMBOL_GPL(k3_phy_calibrate); + +MODULE_DESCRIPTION("SpacemiT K3 PHY common ops"); +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/spacemit/phy-k3-common.h b/drivers/phy/spacemit/phy-k3-common.h new file mode 100644 index 000000000..49009c3c3 --- /dev/null +++ b/drivers/phy/spacemit/phy-k3-common.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _PHY_K3_COMMON_H +#define _PHY_K3_COMMON_H + +#include <linux/phy/phy.h> + +struct k3_phy_lane_group_data { + u32 lanes; + u8 config; + u8 mask; + u32 offsets[] __counted_by(lanes); +}; + +struct k3_lane_group { + const struct k3_phy_lane_group_data *data; + void __iomem *base; + struct phy *phy; + bool is_pcie; +}; + +extern const struct phy_ops k3_pcie_phy_ops; +extern const struct phy_ops k3_usb3_phy_ops; + +int k3_phy_calibrate(struct regmap *apb_spare); + +#endif |
