Add MIPI CSI PHY driver for i.MX8QM, i.MX8QXP and i.MX8ULP. Signed-off-by: Frank Li <Frank.Li@xxxxxxx> --- drivers/phy/freescale/Kconfig | 9 + drivers/phy/freescale/Makefile | 1 + drivers/phy/freescale/phy-fsl-imx8q-mipi-cphy.c | 254 ++++++++++++++++++++++++ 3 files changed, 264 insertions(+) diff --git a/drivers/phy/freescale/Kconfig b/drivers/phy/freescale/Kconfig index dcd9acff6d01a..f412fa405b9b6 100644 --- a/drivers/phy/freescale/Kconfig +++ b/drivers/phy/freescale/Kconfig @@ -35,6 +35,15 @@ config PHY_FSL_IMX8M_PCIE Enable this to add support for the PCIE PHY as found on i.MX8M family of SOCs. +config PHY_FSL_IMX8Q_MIPI_CPHY + tristate "Freescale MIPI CSI PHY support" + depends on OF && HAS_IOMEM + select GENERIC_PHY + select REGMAP_MMIO + help + Enable this to add support for the MIPI CSI PHY as found + on NXP's i.MX8 family of SOCs. + config PHY_FSL_IMX8QM_HSIO tristate "Freescale i.MX8QM HSIO PHY" depends on OF && HAS_IOMEM diff --git a/drivers/phy/freescale/Makefile b/drivers/phy/freescale/Makefile index 658eac7d0a622..8ff72dfdcf654 100644 --- a/drivers/phy/freescale/Makefile +++ b/drivers/phy/freescale/Makefile @@ -3,6 +3,7 @@ obj-$(CONFIG_PHY_FSL_IMX8MQ_USB) += phy-fsl-imx8mq-usb.o obj-$(CONFIG_PHY_MIXEL_LVDS_PHY) += phy-fsl-imx8qm-lvds-phy.o obj-$(CONFIG_PHY_MIXEL_MIPI_DPHY) += phy-fsl-imx8-mipi-dphy.o obj-$(CONFIG_PHY_FSL_IMX8M_PCIE) += phy-fsl-imx8m-pcie.o +obj-$(CONFIG_PHY_FSL_IMX8Q_MIPI_CPHY) += phy-fsl-imx8q-mipi-cphy.o obj-$(CONFIG_PHY_FSL_IMX8QM_HSIO) += phy-fsl-imx8qm-hsio.o obj-$(CONFIG_PHY_FSL_LYNX_28G) += phy-fsl-lynx-28g.o obj-$(CONFIG_PHY_FSL_SAMSUNG_HDMI_PHY) += phy-fsl-samsung-hdmi.o diff --git a/drivers/phy/freescale/phy-fsl-imx8q-mipi-cphy.c b/drivers/phy/freescale/phy-fsl-imx8q-mipi-cphy.c new file mode 100644 index 0000000000000..eff3c9554b95e --- /dev/null +++ b/drivers/phy/freescale/phy-fsl-imx8q-mipi-cphy.c @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2025 NXP + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +struct imx8_mipi_drvdata { + int offset; + bool bsyscon : 1; +}; + +struct imx8_mipi_phy { + struct phy *phy; + struct device *dev; + struct regmap *phy_gpr; + int phy_gpr_reg; + int speed; + const struct imx8_mipi_drvdata *drvdata; +}; + +#define CSI2SS_PL_CLK_INTERVAL_US 10000 +#define CSI2SS_PL_CLK_TIMEOUT_US 100000 + +#define CSI2SS_BASE_OFFSET 0x0 + +#define CSI2SS_PLM_CTRL (CSI2SS_BASE_OFFSET + 0x0) +#define CSI2SS_PLM_CTRL_PL_CLK_RUN BIT(31) +#define CSI2SS_PLM_CTRL_VSYNC_OVERRIDE BIT(9) +#define CSI2SS_PLM_CTRL_HSYNC_OVERRIDE BIT(10) +#define CSI2SS_PLM_CTRL_VALID_OVERRIDE BIT(11) +#define CSI2SS_PLM_CTRL_POLARITY_MASK BIT(12) +#define CSI2SS_PLM_CTRL_ENABLE_PL BIT(0) + +#define CSI2SS_PHY_CTRL (CSI2SS_BASE_OFFSET + 0x4) +#define CSI2SS_PHY_CTRL_PD BIT(22) +#define CSI2SS_PHY_CTRL_RTERM_SEL BIT(21) +#define CSI2SS_PLM_CTRL_POLARITY BIT(12) +#define CSI2SS_PHY_CTRL_RX_HS_SETTLE(x) FIELD_PREP(GENMASK(9, 4), (x)) +#define CSI2SS_PHY_CTRL_CONT_CLK_MODE BIT(3) +#define CSI2SS_PHY_CTRL_DDRCLK_EN BIT(2) +#define CSI2SS_PHY_CTRL_AUTO_PD_EN BIT(1) +#define CSI2SS_PHY_CTRL_RX_ENABLE BIT(0) + +#define CSI2SS_PHY_STATUS (CSI2SS_BASE_OFFSET + 0x8) +#define CSI2SS_PHY_TEST_STATUS (CSI2SS_BASE_OFFSET + 0x10) +#define CSI2SS_PHY_TEST_STATUS_D0 (CSI2SS_BASE_OFFSET + 0x14) +#define CSI2SS_PHY_TEST_STATUS_D1 (CSI2SS_BASE_OFFSET + 0x18) +#define CSI2SS_PHY_TEST_STATUS_D2 (CSI2SS_BASE_OFFSET + 0x1C) +#define CSI2SS_PHY_TEST_STATUS_D3 (CSI2SS_BASE_OFFSET + 0x20) + +#define CSI2SS_VC_INTERLACED (CSI2SS_BASE_OFFSET + 0x30) +#define CSI2SS_VC_INTERLACED_VC3 BIT(3) +#define CSI2SS_VC_INTERLACED_VC2 BIT(2) +#define CSI2SS_VC_INTERLACED_VC1 BIT(1) +#define CSI2SS_VC_INTERLACED_VC0 BIT(0) +#define CSI2SS_VC_INTERLACED_MASK GENMASK(3, 0) + +#define CSI2SS_DATA_TYPE (CSI2SS_BASE_OFFSET + 0x38) +#define CSI2SS_DATA_TYPE_LEGACY_YUV420_8BIT BIT(2) +#define CSI2SS_DATA_TYPE_YUV422_8BIT BIT(6) +#define CSI2SS_DATA_TYPE_YUV422_10BIT BIT(7) +#define CSI2SS_DATA_TYPE_RGB444 BIT(8) +#define CSI2SS_DATA_TYPE_RGB555 BIT(9) +#define CSI2SS_DATA_TYPE_RGB565 BIT(10) +#define CSI2SS_DATA_TYPE_RGB666 BIT(11) +#define CSI2SS_DATA_TYPE_RGB888 BIT(12) +#define CSI2SS_DATA_TYPE_RAW6 BIT(16) +#define CSI2SS_DATA_TYPE_RAW8 BIT(18) +#define CSI2SS_DATA_TYPE_RAW10 BIT(19) + +#define CSI2SS_YUV420_1ST_LINE_DATA_TYPE (CSI2SS_BASE_OFFSET + 0x40) +#define CSI2SS_YUV420_1ST_LINE_DATA_TYPE_ODD 0 +#define CSI2SS_YUV420_1ST_LINE_DATA_TYPE_EVEN 1 + +#define CSI2SS_CTRL_CLK_RESET (CSI2SS_BASE_OFFSET + 0x44) +#define CSI2SS_CTRL_CLK_RESET_EN BIT(0) + +#define CSI2SS_STREAM_FENCE_CTRL (CSI2SS_BASE_OFFSET + 0x48) +#define CSI2SS_STREAM_FENCE_VC3 BIT(3) +#define CSI2SS_STREAM_FENCE_VC2 BIT(2) +#define CSI2SS_STREAM_FENCE_VC1 BIT(1) +#define CSI2SS_STREAM_FENCE_VC0 BIT(0) +#define CSI2SS_STREAM_FENCE_CTRL_MASK GENMASK(3, 0) + +#define CSI2SS_STREAM_FENCE_STATUS (CSI2SS_BASE_OFFSET + 0x4C) + +static int imx8_mipi_phy_power_on(struct phy *phy) +{ + struct imx8_mipi_phy *imx8_phy = phy_get_drvdata(phy); + int ret; + u32 val; + + /* clear format */ + regmap_clear_bits(imx8_phy->phy_gpr, + imx8_phy->phy_gpr_reg + CSI2SS_DATA_TYPE, 0xffffff); + + /* clear polarity */ + regmap_clear_bits(imx8_phy->phy_gpr, + imx8_phy->phy_gpr_reg + CSI2SS_PLM_CTRL, + CSI2SS_PLM_CTRL_VSYNC_OVERRIDE | + CSI2SS_PLM_CTRL_HSYNC_OVERRIDE | + CSI2SS_PLM_CTRL_VALID_OVERRIDE | + CSI2SS_PLM_CTRL_POLARITY_MASK); + + val = CSI2SS_PHY_CTRL_RX_ENABLE | CSI2SS_PHY_CTRL_DDRCLK_EN | + CSI2SS_PHY_CTRL_CONT_CLK_MODE | + CSI2SS_PHY_CTRL_RX_HS_SETTLE(imx8_phy->speed) | + CSI2SS_PHY_CTRL_PD | CSI2SS_PHY_CTRL_RTERM_SEL | + CSI2SS_PHY_CTRL_AUTO_PD_EN; + + regmap_update_bits(imx8_phy->phy_gpr, + imx8_phy->phy_gpr_reg + CSI2SS_PHY_CTRL, 0xffffff, + val); + + ret = regmap_read_poll_timeout(imx8_phy->phy_gpr, + imx8_phy->phy_gpr_reg + CSI2SS_PLM_CTRL, + val, !(val & CSI2SS_PLM_CTRL_PL_CLK_RUN), + CSI2SS_PL_CLK_INTERVAL_US, + CSI2SS_PL_CLK_TIMEOUT_US); + + if (ret) { + dev_err(imx8_phy->dev, "Timeout waiting for Pixel-Link clock"); + return ret; + } + + /* Enable Pixel link Master*/ + regmap_set_bits(imx8_phy->phy_gpr, imx8_phy->phy_gpr_reg + CSI2SS_PLM_CTRL, + CSI2SS_PLM_CTRL_ENABLE_PL | CSI2SS_PLM_CTRL_VALID_OVERRIDE); + + /* PHY Enable */ + regmap_update_bits(imx8_phy->phy_gpr, imx8_phy->phy_gpr_reg + CSI2SS_PHY_CTRL, + CSI2SS_PHY_CTRL_PD | CSI2SS_PLM_CTRL_POLARITY, 0x0); + + /* Release Reset */ + regmap_set_bits(imx8_phy->phy_gpr, imx8_phy->phy_gpr_reg + CSI2SS_CTRL_CLK_RESET, + CSI2SS_CTRL_CLK_RESET_EN); + + return ret; +} + +static int imx8_mipi_phy_power_off(struct phy *phy) +{ + struct imx8_mipi_phy *imx8_phy = phy_get_drvdata(phy); + + /* Disable Pixel Link */ + regmap_write(imx8_phy->phy_gpr, imx8_phy->phy_gpr_reg + CSI2SS_PLM_CTRL, 0x0); + + /* Disable PHY */ + regmap_write(imx8_phy->phy_gpr, imx8_phy->phy_gpr_reg + CSI2SS_PHY_CTRL, 0x0); + + return 0; +}; + +static int imx8_mipi_phy_set_speed(struct phy *phy, int speed) +{ + struct imx8_mipi_phy *imx8_phy = phy_get_drvdata(phy); + + imx8_phy->speed = speed; + + return 0; +} + +static const struct phy_ops imx8_mipi_phy_ops = { + .power_on = imx8_mipi_phy_power_on, + .power_off = imx8_mipi_phy_power_off, + .set_speed = imx8_mipi_phy_set_speed, + .owner = THIS_MODULE, +}; + +const struct imx8_mipi_drvdata imx8qxp_mipi_drvdata = { +}; + +const struct imx8_mipi_drvdata imx8ulp_mipi_drvdata = { + .offset = 0, + .bsyscon = true, +}; + +static const struct of_device_id imx8_mipi_phy_of_match[] = { + { .compatible = "fsl,imx8qxp-mipi-cphy", &imx8qxp_mipi_drvdata }, + { .compatible = "fsl,imx8ulp-mipi-cphy", &imx8ulp_mipi_drvdata }, + {}, +}; +MODULE_DEVICE_TABLE(of, imx8_mipi_phy_of_match); + +static const struct regmap_config regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +static int imx8_mipi_phy_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + struct imx8_mipi_phy *imx8_phy; + void __iomem *base; + + imx8_phy = devm_kzalloc(dev, sizeof(*imx8_phy), GFP_KERNEL); + if (!imx8_phy) + return -ENOMEM; + + imx8_phy->dev = dev; + imx8_phy->drvdata = of_device_get_match_data(dev); + imx8_phy->phy_gpr_reg = imx8_phy->drvdata->offset; + + if (imx8_phy->drvdata->bsyscon) { + imx8_phy->phy_gpr = syscon_node_to_regmap(dev->of_node->parent); + return dev_err_probe(dev, PTR_ERR(imx8_phy->phy_gpr), + "failed to get regmap\n"); + } else { + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return dev_err_probe(dev, IS_ERR(base), + "missed phy base register\n"); + + imx8_phy->phy_gpr = devm_regmap_init_mmio(dev, base, ®map_config); + if (IS_ERR(imx8_phy->phy_gpr)) + return dev_err_probe(dev, PTR_ERR(imx8_phy->phy_gpr), + "unable to find iomuxc registers\n"); + } + + imx8_phy->phy = devm_phy_create(dev, NULL, &imx8_mipi_phy_ops); + if (IS_ERR(imx8_phy->phy)) + return PTR_ERR(imx8_phy->phy); + + phy_set_drvdata(imx8_phy->phy, imx8_phy); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static struct platform_driver imx8_mipi_phy_driver = { + .probe = imx8_mipi_phy_probe, + .driver = { + .name = "imx8-mipi-cphy", + .of_match_table = imx8_mipi_phy_of_match, + } +}; +module_platform_driver(imx8_mipi_phy_driver); + +MODULE_DESCRIPTION("FSL IMX8 MIPI CSI PHY driver"); +MODULE_LICENSE("GPL"); -- 2.34.1