summaryrefslogtreecommitdiffstats
path: root/drivers/phy/axiado
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-19 10:09:22 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-19 10:09:22 -0700
commitf4cdf7ca9a1fdcca413157df19753f388a5a224e (patch)
tree60a31895b2446cd7fa27514e8c2843534a0de0da /drivers/phy/axiado
downloadlinux-stable-f4cdf7ca9a1fdcca413157df19753f388a5a224e.tar.gz
linux-stable-f4cdf7ca9a1fdcca413157df19753f388a5a224e.zip
Merge tag 'media/v7.3-1' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-mediagrafted
Pull media updates from Mauro Carvalho Chehab: - v4l2-core: added ISP statistics support and per-block validation - v4l2-core: Allow unknown HDR10 white point and luminance - New camera sensors: Sony IMX678 and IMX471m, Himax HM1092 IR sensor - New codec: Milos: VPU v2.0 codec support - isp driver: gained support for Dreamchip RPPX1 ISP framework - vsp1 driver: gained support for RZ/T2H and RZ/N2H - Novalake driver: gained CVS support for new NVL hardware - dvb-core: fix feed leak on failed DMX_ADD_PID - several driver fixes, cleanups and minor improvements * tag 'media/v7.3-1' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media: (308 commits) media: ipu-bridge: check all DMI entries when overriding sensor rotation media: v4l2-async: avoid deleting unlinked ASC entry on link error media: rzg2l-cru: Align bytesperline to hardware DMA stride requirement media: intel/ipu6: fix async notifier cleanup leak on parse error media: staging/ipu7: fix async notifier UAF on probe error path media: amd: isp4: fix self-deadlock in isp4sd_pwron_and_init() error path media: amd: isp4: release partial allocations in isp4if_alloc_fw_gpumem() media: rcar-isp: Fix VSPX reference leaks media: rcar-isp: Release ISPCORE resources media: i2c: imx415: Release runtime PM reference on VBLANK error media: i2c: imx415: Return test pattern write errors media: renesas: vsp1: Declare index variables in for loop statement media: renesas: vsp1: Make reset control optional to support platforms without a reset line media: dt-bindings: media: renesas,vsp1: Document RZ/T2H and RZ/N2H SoCs media: dt-bindings: media: renesas,fcp: Document RZ/T2H and RZ/N2H SoCs media: nxp: imx8-isi: Add additional 32-bit RGB format support media: nxp: imx8-isi: Add 16-bit raw Bayer format support media: nxp: imx8-isi: Implement per-stream reference counting for multiplexed streams media: nxp: imx8-isi: Use BIT_ULL() for 64-bit stream masks media: nxp: imx8-isi: Correct color map between V4L2 and ISI ...
Diffstat (limited to 'drivers/phy/axiado')
-rw-r--r--drivers/phy/axiado/Kconfig11
-rw-r--r--drivers/phy/axiado/Makefile1
-rw-r--r--drivers/phy/axiado/phy-axiado-emmc.c217
3 files changed, 229 insertions, 0 deletions
diff --git a/drivers/phy/axiado/Kconfig b/drivers/phy/axiado/Kconfig
new file mode 100644
index 000000000..d159e0345
--- /dev/null
+++ b/drivers/phy/axiado/Kconfig
@@ -0,0 +1,11 @@
+#
+# PHY drivers for Axiado platforms
+#
+
+config PHY_AX3000_EMMC
+ tristate "Axiado eMMC PHY driver"
+ depends on OF && (ARCH_AXIADO || COMPILE_TEST)
+ select GENERIC_PHY
+ help
+ Enables this to support for the AX3000 EMMC PHY driver.
+ If unsure, say N.
diff --git a/drivers/phy/axiado/Makefile b/drivers/phy/axiado/Makefile
new file mode 100644
index 000000000..1e2b1ba01
--- /dev/null
+++ b/drivers/phy/axiado/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_PHY_AX3000_EMMC) += phy-axiado-emmc.o
diff --git a/drivers/phy/axiado/phy-axiado-emmc.c b/drivers/phy/axiado/phy-axiado-emmc.c
new file mode 100644
index 000000000..e0e217477
--- /dev/null
+++ b/drivers/phy/axiado/phy-axiado-emmc.c
@@ -0,0 +1,217 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Axiado eMMC PHY driver
+ *
+ * Copyright (C) 2017 Arasan Chip Systems Inc.
+ * Copyright (C) 2022-2026 Axiado Corporation (or its affiliates).
+ *
+ * Based on Arasan Driver (sdhci-pci-arasan.c)
+ * sdhci-pci-arasan.c - Driver for Arasan PCI Controller with integrated phy.
+ */
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+
+/* Arasan eMMC 5.1 - PHY configuration registers */
+#define CAP_REG_IN_S1_MSB 0x04
+#define PHY_CTRL_1 0x38
+#define PHY_CTRL_2 0x3c
+#define PHY_CTRL_3 0x40
+#define STATUS 0x50
+
+#define DLL_ENBL BIT(26)
+#define RTRIM_EN BIT(21)
+#define PDB_ENBL BIT(23)
+#define RETB_ENBL BIT(1)
+
+#define REN_STRB BIT(27)
+#define REN_CMD_EN GENMASK(20, 12)
+
+/* Pull-UP Enable on CMD Line */
+#define PU_CMD_EN GENMASK(11, 3)
+
+/* Selection value for the optimum delay from 1-32 output tap lines */
+#define OTAP_DLY 0x02
+/* DLL charge pump current trim default [1000] */
+#define DLL_TRM_ICP 0x08
+/* Select the frequency range of DLL Operation */
+#define FRQ_SEL 0x01
+
+#define OTAP_SEL_MASK GENMASK(10, 7)
+#define DLL_TRM_MASK GENMASK(25, 22)
+#define DLL_FRQSEL_MASK GENMASK(27, 25)
+
+#define OTAP_SEL(x) (FIELD_PREP(OTAP_SEL_MASK, x) | OTAPDLY_EN)
+#define DLL_TRM(x) (FIELD_PREP(DLL_TRM_MASK, x) | DLL_ENBL)
+#define DLL_FRQSEL(x) FIELD_PREP(DLL_FRQSEL_MASK, x)
+
+#define OTAPDLY_EN BIT(11)
+
+#define SEL_DLY_RXCLK BIT(18)
+#define SEL_DLY_TXCLK BIT(19)
+
+#define CALDONE_MASK 0x40
+#define DLL_RDY_MASK 0x1
+#define MAX_CLK_BUF0 BIT(20)
+#define MAX_CLK_BUF1 BIT(21)
+#define MAX_CLK_BUF2 BIT(22)
+
+#define CLK_MULTIPLIER 0xc008e
+#define POLL_TIMEOUT_MS 3000
+#define POLL_DELAY_US 100
+
+struct axiado_emmc_phy {
+ void __iomem *reg_base;
+ struct device *dev;
+};
+
+static int axiado_emmc_phy_init(struct phy *phy)
+{
+ struct axiado_emmc_phy *ax_phy = phy_get_drvdata(phy);
+ struct device *dev = ax_phy->dev;
+ u32 val;
+ int ret;
+
+ val = readl(ax_phy->reg_base + PHY_CTRL_1);
+ writel(val | RETB_ENBL | RTRIM_EN, ax_phy->reg_base + PHY_CTRL_1);
+
+ val = readl(ax_phy->reg_base + PHY_CTRL_3);
+ writel(val | PDB_ENBL, ax_phy->reg_base + PHY_CTRL_3);
+
+ ret = readl_poll_timeout(ax_phy->reg_base + STATUS, val,
+ val & CALDONE_MASK, POLL_DELAY_US,
+ POLL_TIMEOUT_MS * 1000);
+ if (ret) {
+ dev_err(dev, "PHY calibration timeout\n");
+ return ret;
+ }
+
+ val = readl(ax_phy->reg_base + PHY_CTRL_1);
+ writel(val | REN_CMD_EN | PU_CMD_EN, ax_phy->reg_base + PHY_CTRL_1);
+
+ val = readl(ax_phy->reg_base + PHY_CTRL_2);
+ writel(val | REN_STRB, ax_phy->reg_base + PHY_CTRL_2);
+
+ val = readl(ax_phy->reg_base + PHY_CTRL_3);
+ writel(val | MAX_CLK_BUF0 | MAX_CLK_BUF1 | MAX_CLK_BUF2,
+ ax_phy->reg_base + PHY_CTRL_3);
+
+ writel(CLK_MULTIPLIER, ax_phy->reg_base + CAP_REG_IN_S1_MSB);
+
+ val = readl(ax_phy->reg_base + PHY_CTRL_3);
+ writel(val | SEL_DLY_RXCLK | SEL_DLY_TXCLK,
+ ax_phy->reg_base + PHY_CTRL_3);
+
+ return 0;
+}
+
+static int axiado_emmc_phy_power_on(struct phy *phy)
+{
+ struct axiado_emmc_phy *ax_phy = phy_get_drvdata(phy);
+ struct device *dev = ax_phy->dev;
+ u32 val;
+ int ret;
+
+ val = readl(ax_phy->reg_base + PHY_CTRL_1);
+ writel(val | RETB_ENBL, ax_phy->reg_base + PHY_CTRL_1);
+
+ val = readl(ax_phy->reg_base + PHY_CTRL_3);
+ writel(val | PDB_ENBL, ax_phy->reg_base + PHY_CTRL_3);
+
+ val = readl(ax_phy->reg_base + PHY_CTRL_2);
+ writel(val | OTAP_SEL(OTAP_DLY), ax_phy->reg_base + PHY_CTRL_2);
+
+ val = readl(ax_phy->reg_base + PHY_CTRL_1);
+ writel(val | DLL_TRM(DLL_TRM_ICP), ax_phy->reg_base + PHY_CTRL_1);
+
+ val = readl(ax_phy->reg_base + PHY_CTRL_3);
+ writel(val | DLL_FRQSEL(FRQ_SEL), ax_phy->reg_base + PHY_CTRL_3);
+
+ ret = read_poll_timeout(readl, val, val & DLL_RDY_MASK, POLL_DELAY_US,
+ POLL_TIMEOUT_MS * 1000, false,
+ ax_phy->reg_base + STATUS);
+ if (ret) {
+ dev_err(dev, "DLL ready timeout\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int axiado_emmc_phy_power_off(struct phy *phy)
+{
+ struct axiado_emmc_phy *ax_phy = phy_get_drvdata(phy);
+ u32 val;
+
+ val = readl(ax_phy->reg_base + PHY_CTRL_1);
+ val &= ~(DLL_TRM_MASK | DLL_ENBL);
+ writel(val, ax_phy->reg_base + PHY_CTRL_1);
+
+ val = readl(ax_phy->reg_base + PHY_CTRL_3);
+ val &= ~(DLL_FRQSEL_MASK | PDB_ENBL);
+ writel(val, ax_phy->reg_base + PHY_CTRL_3);
+
+ return 0;
+}
+
+static const struct phy_ops axiado_emmc_phy_ops = {
+ .init = axiado_emmc_phy_init,
+ .power_on = axiado_emmc_phy_power_on,
+ .power_off = axiado_emmc_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static const struct of_device_id axiado_emmc_phy_of_match[] = {
+ { .compatible = "axiado,ax3000-emmc-phy" },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, axiado_emmc_phy_of_match);
+
+static int axiado_emmc_phy_probe(struct platform_device *pdev)
+{
+ struct axiado_emmc_phy *ax_phy;
+ struct phy_provider *phy_provider;
+ struct device *dev = &pdev->dev;
+ struct phy *generic_phy;
+
+ if (!dev->of_node)
+ return -ENODEV;
+
+ ax_phy = devm_kzalloc(dev, sizeof(*ax_phy), GFP_KERNEL);
+ if (!ax_phy)
+ return -ENOMEM;
+
+ ax_phy->reg_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ax_phy->reg_base))
+ return PTR_ERR(ax_phy->reg_base);
+
+ ax_phy->dev = dev;
+
+ generic_phy = devm_phy_create(dev, dev->of_node, &axiado_emmc_phy_ops);
+ if (IS_ERR(generic_phy))
+ return dev_err_probe(dev, PTR_ERR(generic_phy),
+ "failed to create PHY\n");
+
+ phy_set_drvdata(generic_phy, ax_phy);
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static struct platform_driver axiado_emmc_phy_driver = {
+ .probe = axiado_emmc_phy_probe,
+ .driver = {
+ .name = "axiado-emmc-phy",
+ .of_match_table = axiado_emmc_phy_of_match,
+ },
+};
+module_platform_driver(axiado_emmc_phy_driver);
+
+MODULE_DESCRIPTION("AX3000 eMMC PHY Driver");
+MODULE_AUTHOR("Axiado Corporation");
+MODULE_LICENSE("GPL");