mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-05-15 23:41:35 -04:00
phy: usb: Add driver for Canaan K230 USB 2.0 PHY
Add driver for the USB 2.0 PHY in Canaan K230 SoC, which supports PHY initialization and power management. Add Kconfig/Makefile under drivers/phy/canaan/. Signed-off-by: Jiayu Du <jiayu.riscv@isrc.iscas.ac.cn> Link: https://patch.msgid.link/20260121145526.14672-4-jiayu.riscv@isrc.iscas.ac.cn Signed-off-by: Vinod Koul <vkoul@kernel.org>
This commit is contained in:
@@ -139,6 +139,7 @@ source "drivers/phy/amlogic/Kconfig"
|
||||
source "drivers/phy/apple/Kconfig"
|
||||
source "drivers/phy/broadcom/Kconfig"
|
||||
source "drivers/phy/cadence/Kconfig"
|
||||
source "drivers/phy/canaan/Kconfig"
|
||||
source "drivers/phy/freescale/Kconfig"
|
||||
source "drivers/phy/hisilicon/Kconfig"
|
||||
source "drivers/phy/ingenic/Kconfig"
|
||||
|
||||
@@ -22,6 +22,7 @@ obj-$(CONFIG_GENERIC_PHY) += allwinner/ \
|
||||
apple/ \
|
||||
broadcom/ \
|
||||
cadence/ \
|
||||
canaan/ \
|
||||
freescale/ \
|
||||
hisilicon/ \
|
||||
ingenic/ \
|
||||
|
||||
14
drivers/phy/canaan/Kconfig
Normal file
14
drivers/phy/canaan/Kconfig
Normal file
@@ -0,0 +1,14 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# Phy drivers for Canaan platforms
|
||||
#
|
||||
config PHY_CANAAN_USB
|
||||
tristate "Canaan USB2 PHY Driver"
|
||||
depends on (ARCH_CANAAN || COMPILE_TEST) && OF
|
||||
select GENERIC_PHY
|
||||
help
|
||||
Enable this driver to support the USB 2.0 PHY controller
|
||||
on Canaan K230 RISC-V SoCs. This PHY controller
|
||||
provides physical layer functionality for USB 2.0 devices.
|
||||
If you have a Canaan K230 board and need USB 2.0 support,
|
||||
say Y or M here.
|
||||
2
drivers/phy/canaan/Makefile
Normal file
2
drivers/phy/canaan/Makefile
Normal file
@@ -0,0 +1,2 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
obj-$(CONFIG_PHY_CANAAN_USB) += phy-k230-usb.o
|
||||
284
drivers/phy/canaan/phy-k230-usb.c
Normal file
284
drivers/phy/canaan/phy-k230-usb.c
Normal file
@@ -0,0 +1,284 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Canaan usb PHY driver
|
||||
*
|
||||
* Copyright (C) 2026 Jiayu Du <jiayu.riscv@isrc.iscas.ac.cn>
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#define MAX_PHYS 2
|
||||
|
||||
/* Register offsets within the HiSysConfig system controller */
|
||||
#define K230_USB0_TEST_REG_BASE 0x70
|
||||
#define K230_USB0_CTL_REG_BASE 0xb0
|
||||
#define K230_USB1_TEST_REG_BASE 0x90
|
||||
#define K230_USB1_CTL_REG_BASE 0xb8
|
||||
|
||||
/* Relative offsets within each PHY's control/test block */
|
||||
#define CTL0_OFFSET 0x00
|
||||
#define CTL1_OFFSET 0x04
|
||||
#define TEST_CTL3_OFFSET 0x0c
|
||||
|
||||
/* Bit definitions for TEST_CTL3 */
|
||||
#define USB_IDPULLUP0 BIT(4)
|
||||
#define USB_DMPULLDOWN0 BIT(8)
|
||||
#define USB_DPPULLDOWN0 BIT(9)
|
||||
|
||||
/* USB control register 0 in HiSysConfig system controller */
|
||||
/* PLL Integral Path Tune */
|
||||
#define USB_CTL0_PLLITUNE_MASK GENMASK(23, 22)
|
||||
|
||||
/* PLL Proportional Path Tune */
|
||||
#define USB_CTL0_PLLPTUNE_MASK GENMASK(21, 18)
|
||||
|
||||
/* PLL Bandwidth Adjustment */
|
||||
#define USB_CTL0_PLLBTUNE_MASK GENMASK(17, 17)
|
||||
|
||||
/* VReg18 Bypass Control */
|
||||
#define USB_CTL0_VREGBYPASS_MASK GENMASK(16, 16)
|
||||
|
||||
/* Retention Mode Enable */
|
||||
#define USB_CTL0_RETENABLEN_MASK GENMASK(15, 15)
|
||||
|
||||
/* Reserved Request Input */
|
||||
#define USB_CTL0_RESREQIN_MASK GENMASK(14, 14)
|
||||
|
||||
/* External VBUS Valid Select */
|
||||
#define USB_CTL0_VBUSVLDEXTSEL0_MASK GENMASK(13, 13)
|
||||
|
||||
/* OTG Block Disable Control */
|
||||
#define USB_CTL0_OTGDISABLE0_MASK GENMASK(12, 12)
|
||||
|
||||
/* Drive VBUS Enable */
|
||||
#define USB_CTL0_DRVVBUS0_MASK GENMASK(11, 11)
|
||||
|
||||
/* Autoresume Mode Enable */
|
||||
#define USB_CTL0_AUTORSMENB0_MASK GENMASK(10, 10)
|
||||
|
||||
/* HS Transceiver Asynchronous Control */
|
||||
#define USB_CTL0_HSXCVREXTCTL0_MASK GENMASK(9, 9)
|
||||
|
||||
/* USB 1.1 Transmit Data */
|
||||
#define USB_CTL0_FSDATAEXT0_MASK GENMASK(8, 8)
|
||||
|
||||
/* USB 1.1 SE0 Generation */
|
||||
#define USB_CTL0_FSSE0EXT0_MASK GENMASK(7, 7)
|
||||
|
||||
/* USB 1.1 Data Enable */
|
||||
#define USB_CTL0_TXENABLEN0_MASK GENMASK(6, 6)
|
||||
|
||||
/* Disconnect Threshold */
|
||||
#define USB_CTL0_COMPDISTUNE0_MASK GENMASK(5, 3)
|
||||
|
||||
/* Squelch Threshold */
|
||||
#define USB_CTL0_SQRXTUNE0_MASK GENMASK(2, 0)
|
||||
|
||||
/* USB control register 1 in HiSysConfig system controller */
|
||||
/* Data Detect Voltage */
|
||||
#define USB_CTL1_VDATREFTUNE0_MASK GENMASK(23, 22)
|
||||
|
||||
/* VBUS Valid Threshold */
|
||||
#define USB_CTL1_OTGTUNE0_MASK GENMASK(21, 19)
|
||||
|
||||
/* Transmitter High-Speed Crossover */
|
||||
#define USB_CTL1_TXHSXVTUNE0_MASK GENMASK(18, 17)
|
||||
|
||||
/* FS/LS Source Impedance */
|
||||
#define USB_CTL1_TXFSLSTUNE0_MASK GENMASK(16, 13)
|
||||
|
||||
/* HS DC Voltage Level */
|
||||
#define USB_CTL1_TXVREFTUNE0_MASK GENMASK(12, 9)
|
||||
|
||||
/* HS Transmitter Rise/Fall Time */
|
||||
#define USB_CTL1_TXRISETUNE0_MASK GENMASK(8, 7)
|
||||
|
||||
/* USB Source Impedance */
|
||||
#define USB_CTL1_TXRESTUNE0_MASK GENMASK(6, 5)
|
||||
|
||||
/* HS Transmitter Pre-Emphasis Current Control */
|
||||
#define USB_CTL1_TXPREEMPAMPTUNE0_MASK GENMASK(4, 3)
|
||||
|
||||
/* HS Transmitter Pre-Emphasis Duration Control */
|
||||
#define USB_CTL1_TXPREEMPPULSETUNE0_MASK GENMASK(2, 2)
|
||||
|
||||
/* charging detection */
|
||||
#define USB_CTL1_CHRGSRCPUENB0_MASK GENMASK(1, 0)
|
||||
|
||||
#define K230_PHY_CTL0_VAL \
|
||||
( \
|
||||
FIELD_PREP(USB_CTL0_PLLITUNE_MASK, 0x0) | \
|
||||
FIELD_PREP(USB_CTL0_PLLPTUNE_MASK, 0xc) | \
|
||||
FIELD_PREP(USB_CTL0_PLLBTUNE_MASK, 0x1) | \
|
||||
FIELD_PREP(USB_CTL0_VREGBYPASS_MASK, 0x1) | \
|
||||
FIELD_PREP(USB_CTL0_RETENABLEN_MASK, 0x1) | \
|
||||
FIELD_PREP(USB_CTL0_RESREQIN_MASK, 0x0) | \
|
||||
FIELD_PREP(USB_CTL0_VBUSVLDEXTSEL0_MASK, 0x0) | \
|
||||
FIELD_PREP(USB_CTL0_OTGDISABLE0_MASK, 0x0) | \
|
||||
FIELD_PREP(USB_CTL0_DRVVBUS0_MASK, 0x1) | \
|
||||
FIELD_PREP(USB_CTL0_AUTORSMENB0_MASK, 0x0) | \
|
||||
FIELD_PREP(USB_CTL0_HSXCVREXTCTL0_MASK, 0x0) | \
|
||||
FIELD_PREP(USB_CTL0_FSDATAEXT0_MASK, 0x0) | \
|
||||
FIELD_PREP(USB_CTL0_FSSE0EXT0_MASK, 0x0) | \
|
||||
FIELD_PREP(USB_CTL0_TXENABLEN0_MASK, 0x0) | \
|
||||
FIELD_PREP(USB_CTL0_COMPDISTUNE0_MASK, 0x3) | \
|
||||
FIELD_PREP(USB_CTL0_SQRXTUNE0_MASK, 0x3) \
|
||||
)
|
||||
|
||||
#define K230_PHY_CTL1_VAL \
|
||||
( \
|
||||
FIELD_PREP(USB_CTL1_VDATREFTUNE0_MASK, 0x1) | \
|
||||
FIELD_PREP(USB_CTL1_OTGTUNE0_MASK, 0x3) | \
|
||||
FIELD_PREP(USB_CTL1_TXHSXVTUNE0_MASK, 0x3) | \
|
||||
FIELD_PREP(USB_CTL1_TXFSLSTUNE0_MASK, 0x3) | \
|
||||
FIELD_PREP(USB_CTL1_TXVREFTUNE0_MASK, 0x3) | \
|
||||
FIELD_PREP(USB_CTL1_TXRISETUNE0_MASK, 0x1) | \
|
||||
FIELD_PREP(USB_CTL1_TXRESTUNE0_MASK, 0x1) | \
|
||||
FIELD_PREP(USB_CTL1_TXPREEMPAMPTUNE0_MASK, 0x0) | \
|
||||
FIELD_PREP(USB_CTL1_TXPREEMPPULSETUNE0_MASK, 0x0) | \
|
||||
FIELD_PREP(USB_CTL1_CHRGSRCPUENB0_MASK, 0x0) \
|
||||
)
|
||||
|
||||
struct k230_usb_phy_instance {
|
||||
struct k230_usb_phy_global *global;
|
||||
struct phy *phy;
|
||||
u32 test_offset;
|
||||
u32 ctl_offset;
|
||||
int index;
|
||||
};
|
||||
|
||||
struct k230_usb_phy_global {
|
||||
struct k230_usb_phy_instance phys[MAX_PHYS];
|
||||
void __iomem *base;
|
||||
};
|
||||
|
||||
static int k230_usb_phy_power_on(struct phy *phy)
|
||||
{
|
||||
struct k230_usb_phy_instance *inst = phy_get_drvdata(phy);
|
||||
struct k230_usb_phy_global *global = inst->global;
|
||||
void __iomem *base = global->base;
|
||||
u32 val;
|
||||
|
||||
/* Apply recommended settings */
|
||||
writel(K230_PHY_CTL0_VAL, base + inst->ctl_offset + CTL0_OFFSET);
|
||||
writel(K230_PHY_CTL1_VAL, base + inst->ctl_offset + CTL1_OFFSET);
|
||||
|
||||
/* Configure test register (pull-ups/pull-downs) */
|
||||
val = readl(base + inst->test_offset + TEST_CTL3_OFFSET);
|
||||
val |= USB_IDPULLUP0;
|
||||
|
||||
if (inst->index == 1)
|
||||
val |= (USB_DMPULLDOWN0 | USB_DPPULLDOWN0);
|
||||
else
|
||||
val &= ~(USB_DMPULLDOWN0 | USB_DPPULLDOWN0);
|
||||
|
||||
writel(val, base + inst->test_offset + TEST_CTL3_OFFSET);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int k230_usb_phy_power_off(struct phy *phy)
|
||||
{
|
||||
struct k230_usb_phy_instance *inst = phy_get_drvdata(phy);
|
||||
struct k230_usb_phy_global *global = inst->global;
|
||||
void __iomem *base = global->base;
|
||||
u32 val;
|
||||
|
||||
val = readl(base + inst->test_offset + TEST_CTL3_OFFSET);
|
||||
val &= ~(USB_DMPULLDOWN0 | USB_DPPULLDOWN0);
|
||||
writel(val, base + inst->test_offset + TEST_CTL3_OFFSET);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct phy_ops k230_usb_phy_ops = {
|
||||
.power_on = k230_usb_phy_power_on,
|
||||
.power_off = k230_usb_phy_power_off,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static struct phy *k230_usb_phy_xlate(struct device *dev,
|
||||
const struct of_phandle_args *args)
|
||||
{
|
||||
struct k230_usb_phy_global *global = dev_get_drvdata(dev);
|
||||
unsigned int idx = args->args[0];
|
||||
|
||||
if (idx >= MAX_PHYS)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
return global->phys[idx].phy;
|
||||
}
|
||||
|
||||
static int k230_usb_phy_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct k230_usb_phy_global *global;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct phy_provider *provider;
|
||||
int i;
|
||||
|
||||
global = devm_kzalloc(dev, sizeof(*global), GFP_KERNEL);
|
||||
if (!global)
|
||||
return -ENOMEM;
|
||||
dev_set_drvdata(dev, global);
|
||||
|
||||
global->base = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(global->base))
|
||||
return dev_err_probe(dev, PTR_ERR(global->base),
|
||||
"failed to map registers\n");
|
||||
|
||||
static const struct {
|
||||
u32 test_offset;
|
||||
u32 ctl_offset;
|
||||
} phy_reg_info[MAX_PHYS] = {
|
||||
[0] = { K230_USB0_TEST_REG_BASE, K230_USB0_CTL_REG_BASE },
|
||||
[1] = { K230_USB1_TEST_REG_BASE, K230_USB1_CTL_REG_BASE },
|
||||
};
|
||||
|
||||
for (i = 0; i < MAX_PHYS; i++) {
|
||||
struct k230_usb_phy_instance *inst = &global->phys[i];
|
||||
struct phy *phy;
|
||||
|
||||
inst->global = global;
|
||||
inst->index = i;
|
||||
inst->test_offset = phy_reg_info[i].test_offset;
|
||||
inst->ctl_offset = phy_reg_info[i].ctl_offset;
|
||||
|
||||
phy = devm_phy_create(dev, NULL, &k230_usb_phy_ops);
|
||||
if (IS_ERR(phy)) {
|
||||
dev_err(dev, "failed to create phy%d\n", i);
|
||||
return PTR_ERR(phy);
|
||||
}
|
||||
|
||||
phy_set_drvdata(phy, inst);
|
||||
inst->phy = phy;
|
||||
}
|
||||
|
||||
provider = devm_of_phy_provider_register(dev, k230_usb_phy_xlate);
|
||||
if (IS_ERR(provider))
|
||||
return PTR_ERR(provider);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id k230_usb_phy_of_match[] = {
|
||||
{ .compatible = "canaan,k230-usb-phy" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, k230_usb_phy_of_match);
|
||||
|
||||
static struct platform_driver k230_usb_phy_driver = {
|
||||
.probe = k230_usb_phy_probe,
|
||||
.driver = {
|
||||
.name = "k230-usb-phy",
|
||||
.of_match_table = k230_usb_phy_of_match,
|
||||
},
|
||||
};
|
||||
module_platform_driver(k230_usb_phy_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Canaan Kendryte K230 USB 2.0 PHY driver");
|
||||
MODULE_AUTHOR("Jiayu Du <jiayu.riscv@isrc.iscas.ac.cn>");
|
||||
MODULE_LICENSE("GPL");
|
||||
Reference in New Issue
Block a user