Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions Documentation/devicetree/bindings/phy/spacemit,k3-comb-phy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/phy/spacemit,k3-comb-phy.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#

title: Spacemit K3 PCIE/USB3 Comb PHY

maintainers:
- Inochi Amaoto <inochiama@gmail.com>

properties:
compatible:
const: spacemit,k3-comb-phy

reg:
maxItems: 1

"#phy-cells":
const: 2
description:
The first one is phy id, the second one is phy type.

spacemit,apb-spare:
$ref: /schemas/types.yaml#/definitions/phandle
description:
Phandle to APB SPARE system controller interface, used for
PHY calibration.

spacemit,apmu:
$ref: /schemas/types.yaml#/definitions/phandle-array
items:
- items:
- description: phandle of APMU syscon
- description: configuration of the PHY lanes
description: |
Phandle to control PHY mux configuration. The configuration
is described as follows:
bit 4: 0 - PCIe A x8 mode, 1 - PCIe lane share mode
bit 3: 0 - PCIe A x4 mode, 1 - PCIe A x2 and PCIe B x2 mode
bit 2: 0 - PCIe C lane 0 is PCIe mode , 1 - USB mode
bit 1: 0 - PCIe C lane 1 is PCIe mode , 1 - USB mode
bit 0: 0 - PCIe D lane is PCIe mode , 1 - USB mode

The bit[3:0] is only valid when bit 4 is 1.

required:
- compatible
- "#phy-cells"
- spacemit,apb-spare
- spacemit,apmu

additionalProperties: false

examples:
- |
phy@81d00000 {
compatible = "spacemit,k3-comb-phy";
reg = <0x81d00000 0x600000>;
#phy-cells = <2>;
spacemit,apb-spare = <&apb_spare>;
spacemit,apmu = <&apmu 0x00>;
};
16 changes: 16 additions & 0 deletions drivers/phy/spacemit/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,19 @@ config PHY_SPACEMIT_K1_USB2
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.
2 changes: 2 additions & 0 deletions drivers/phy/spacemit/Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +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-combphy.o
obj-$(CONFIG_PHY_SPACEMIT_K3_COMMON_OPS) += phy-k3-common.o
250 changes: 250 additions & 0 deletions drivers/phy/spacemit/phy-k3-combphy.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* phy-k3-usb3.c - SpacemiT K3 Type-C Orientation Switch 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_comb_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 = 0x00,
.mask = 0xff,
.offsets = {
0x0, 0x400
},
};

static const struct k3_phy_lane_group_data k3_combphy_lane_group1 = {
.lanes = 2,
.config = 0x00,
.mask = 0xff,
.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 = 0x00,
.mask = 0xff,
.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_comb_phy_init_lanes(struct k3_comb_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;

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_comb_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_comb_phy_xlate(struct device *dev, const struct of_phandle_args *args)
{
struct k3_comb_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_comb_phy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
struct phy_provider *provider;
struct k3_comb_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 fine 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(phy->apb_spare),
"Failed to fine APMU syscon");

ret = k3_comb_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_comb_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_comb_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_comb_phy_of_match[] = {
{ .compatible = "spacemit,k3-comb-phy" },
{ },
};
MODULE_DEVICE_TABLE(of, k3_comb_phy_of_match);

static struct platform_driver k3_comb_phy_driver = {
.probe = k3_comb_phy_probe,
.driver = {
.name = "spacemit,k3-comb-phy",
.of_match_table = k3_comb_phy_of_match,
},
};
module_platform_driver(k3_comb_phy_driver);

MODULE_DESCRIPTION("SpacemiT K3 USB3/PCIe comb PHY driver");
MODULE_LICENSE("GPL");
Loading
Loading