diff --git a/Documentation/devicetree/bindings/phy/mixel,mipi-dsi-phy.txt b/Documentation/devicetree/bindings/phy/mixel,mipi-dsi-phy.txt new file mode 100644 index 000000000000..c868b39a88b7 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/mixel,mipi-dsi-phy.txt @@ -0,0 +1,23 @@ +Mixel DSI DPHY for MX8 + +The MIPI-DSI DPHY IP block found on MX8 platforms comes along the MIPI-DSI +IP from Northwest Logic and represents the physical layer for the electrical +signals for DSI. + +Required properties: +- compatible: "mixel,-mipi-dsi-phy" +- reg: the register range of the PHY controller +- #phy-cells: number of cells in PHY, as defined in + Documentation/devicetree/bindings/phy/phy-bindings.txt + this must be <0> + +Optional properties: +- power-domains: phandle to power domain + +Example: + mipi0_phy: mipi0_phy@56228300 { + compatible = "mixel,imx8qm-mipi-dsi-phy"; + reg = <0x0 0x56228300 0x0 0x100>; + power-domains = <&pd_mipi0>; + #phy-cells = <0>; + }; diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index f8f960e62c1d..c03c66281991 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -52,6 +52,12 @@ config PHY_MIXEL_LVDS_COMBO select GENERIC_PHY default ARCH_FSL_IMX8QXP +config PHY_MIXEL_MIPI_DSI + bool + depends on OF + select GENERIC_PHY + default ARCH_FSL_IMX8QM || ARCH_FSL_IMX8QXP + config PHY_FSL_IMX8MQ_USB bool depends on OF diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index feb743fc615d..94e7ca81151a 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_PHY_XGENE) += phy-xgene.o obj-$(CONFIG_PHY_PISTACHIO_USB) += phy-pistachio-usb.o obj-$(CONFIG_PHY_MIXEL_LVDS) += phy-mixel-lvds.o obj-$(CONFIG_PHY_MIXEL_LVDS_COMBO) += phy-mixel-lvds-combo.o +obj-$(CONFIG_PHY_MIXEL_MIPI_DSI) += phy-mixel-mipi-dsi.o obj-$(CONFIG_PHY_FSL_IMX8MQ_USB) += phy-fsl-imx8mq-usb.o obj-$(CONFIG_ARCH_SUNXI) += allwinner/ obj-$(CONFIG_ARCH_MESON) += amlogic/ diff --git a/drivers/phy/phy-mixel-mipi-dsi.c b/drivers/phy/phy-mixel-mipi-dsi.c new file mode 100644 index 000000000000..d62f439c4bc4 --- /dev/null +++ b/drivers/phy/phy-mixel-mipi-dsi.c @@ -0,0 +1,402 @@ +/* + * Copyright 2017 NXP + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DPHY_PD_DPHY 0x00 +#define DPHY_M_PRG_HS_PREPARE 0x04 +#define DPHY_MC_PRG_HS_PREPARE 0x08 +#define DPHY_M_PRG_HS_ZERO 0x0c +#define DPHY_MC_PRG_HS_ZERO 0x10 +#define DPHY_M_PRG_HS_TRAIL 0x14 +#define DPHY_MC_PRG_HS_TRAIL 0x18 +#define DPHY_PD_PLL 0x1c +#define DPHY_TST 0x20 +#define DPHY_CN 0x24 +#define DPHY_CM 0x28 +#define DPHY_CO 0x2c +#define DPHY_LOCK 0x30 +#define DPHY_LOCK_BYP 0x34 +#define DPHY_TX_RCAL 0x38 +#define DPHY_AUTO_PD_EN 0x3c +#define DPHY_RXLPRP 0x40 +#define DPHY_RXCDRP 0x44 + +#define MBPS(x) ((x) * 1000000) + +#define DATA_RATE_MAX_SPEED MBPS(1500) +#define DATA_RATE_MIN_SPEED MBPS(80) + +#define CN_BUF 0xcb7a89c0 +#define CO_BUF 0x63 +#define CM(x) ( \ + ((x) < 32)?0xe0|((x)-16) : \ + ((x) < 64)?0xc0|((x)-32) : \ + ((x) < 128)?0x80|((x)-64) : \ + ((x) - 128)) +#define CN(x) (((x) == 1)?0x1f : (((CN_BUF)>>((x)-1))&0x1f)) +#define CO(x) ((CO_BUF)>>(8-(x))&0x3) + +/* PHY power on is LOW_ENABLE */ +#define PWR_ON 0 +#define PWR_OFF 1 + +struct pll_divider { + u32 cm; + u32 cn; + u32 co; +}; + +struct mixel_mipi_phy_priv { + struct device *dev; + void __iomem *base; + bool have_sc; + sc_rsrc_t mipi_id; + struct pll_divider divider; + struct mutex lock; + unsigned long data_rate; +}; + +struct devtype { + bool have_sc; +}; + +static inline u32 phy_read(struct phy *phy, unsigned int reg) +{ + struct mixel_mipi_phy_priv *priv = phy_get_drvdata(phy); + + return readl(priv->base + reg); +} + +static inline void phy_write(struct phy *phy, u32 value, unsigned int reg) +{ + struct mixel_mipi_phy_priv *priv = phy_get_drvdata(phy); + + writel(value, priv->base + reg); +} + +/* + * mixel_phy_mipi_set_phy_speed: + * Input params: + * bit_clk: PHY PLL needed output clock + * ref_clk: reference input clock for the PHY PLL + * + * Returns: + * 0: if the bit_clk can be achieved for the given ref_clk + * -EINVAL: otherwise + */ +int mixel_phy_mipi_set_phy_speed(struct phy *phy, + unsigned long bit_clk, + unsigned long ref_clk) +{ + struct mixel_mipi_phy_priv *priv = dev_get_drvdata(phy->dev.parent); + u32 div_rate; + u32 numerator = 0; + u32 denominator = 1; + + if (bit_clk > DATA_RATE_MAX_SPEED || bit_clk < DATA_RATE_MIN_SPEED) + return -EINVAL; + + /* simulated fixed point with 3 decimals */ + div_rate = (bit_clk * 1000) / ref_clk; + + while (denominator <= 256) { + if (div_rate % 1000 == 0) + numerator = div_rate / 1000; + if (numerator > 15) + break; + denominator = denominator << 1; + div_rate = div_rate << 1; + } + + /* CM ranges between 16 and 255 */ + /* CN ranges between 1 and 32 */ + /* CO is power of 2: 1, 2, 4, 8 */ + + if (numerator < 16 || numerator > 255) + return -EINVAL; + + numerator = DIV_ROUND_UP(numerator, denominator) * denominator; + + priv->divider.cn = 1; + if (denominator > 8) { + priv->divider.cn = denominator >> 3; + denominator = 8; + } + priv->divider.co = denominator; + priv->divider.cm = numerator; + + priv->data_rate = bit_clk; + + return 0; +} +EXPORT_SYMBOL_GPL(mixel_phy_mipi_set_phy_speed); + +static int mixel_mipi_phy_enable(struct phy *phy, u32 reset) +{ + struct mixel_mipi_phy_priv *priv = phy_get_drvdata(phy); + sc_err_t sci_err = 0; + sc_ipc_t ipc_handle = 0; + u32 mu_id; + + sci_err = sc_ipc_getMuID(&mu_id); + if (sci_err != SC_ERR_NONE) { + dev_err(&phy->dev, "Failed to get MU ID (%d)\n", sci_err); + return -ENODEV; + } + sci_err = sc_ipc_open(&ipc_handle, mu_id); + if (sci_err != SC_ERR_NONE) { + dev_err(&phy->dev, "Failed to open IPC (%d)\n", sci_err); + return -ENODEV; + } + + sci_err = sc_misc_set_control(ipc_handle, + priv->mipi_id, + SC_C_PHY_RESET, + reset); + if (sci_err != SC_ERR_NONE) { + dev_err(&phy->dev, "Failed to reset DPHY (%d)\n", sci_err); + sc_ipc_close(ipc_handle); + return -ENODEV; + } + + sc_ipc_close(ipc_handle); + + return 0; +} + +/* + * We tried our best here to use the values as specified in + * Reference Manual, but we got unstable results. So, these values + * are hacked from their original explanation as found in RM. + */ +static void mixel_phy_set_prg_regs(struct phy *phy) +{ + struct mixel_mipi_phy_priv *priv = phy_get_drvdata(phy); + u32 step; + u32 step_num; + u32 step_max; + + /* MC_PRG_HS_PREPARE */ + if (priv->data_rate > MBPS(1000)) + phy_write(phy, 0x01, DPHY_MC_PRG_HS_PREPARE); + else + phy_write(phy, 0x00, DPHY_MC_PRG_HS_PREPARE); + + /* M_PRG_HS_PREPARE */ + if (priv->data_rate > MBPS(250)) + phy_write(phy, 0x00, DPHY_M_PRG_HS_PREPARE); + else + phy_write(phy, 0x01, DPHY_M_PRG_HS_PREPARE); + + /* MC_PRG_HS_ZERO */ + step_max = 48; + step = (DATA_RATE_MAX_SPEED - DATA_RATE_MIN_SPEED) / step_max; + step_num = ((priv->data_rate - DATA_RATE_MIN_SPEED) / step) + 1; + phy_write(phy, step_num, DPHY_MC_PRG_HS_ZERO); + + /* M_PRG_HS_ZERO */ + if (priv->data_rate < MBPS(1000)) + phy_write(phy, 0x09, DPHY_M_PRG_HS_ZERO); + else + phy_write(phy, 0x10, DPHY_M_PRG_HS_ZERO); + + /* MC_PRG_HS_TRAIL and M_PRG_HS_TRAIL */ + if (priv->data_rate < MBPS(1000)) { + phy_write(phy, 0x05, DPHY_MC_PRG_HS_TRAIL); + phy_write(phy, 0x05, DPHY_M_PRG_HS_TRAIL); + } else if (priv->data_rate < MBPS(1500)) { + phy_write(phy, 0x0C, DPHY_MC_PRG_HS_TRAIL); + phy_write(phy, 0x0C, DPHY_M_PRG_HS_TRAIL); + } else { + phy_write(phy, 0x0F, DPHY_MC_PRG_HS_TRAIL); + phy_write(phy, 0x0F, DPHY_M_PRG_HS_TRAIL); + } +} + +int mixel_mipi_phy_init(struct phy *phy) +{ + struct mixel_mipi_phy_priv *priv = dev_get_drvdata(phy->dev.parent); + + mutex_lock(&priv->lock); + + mixel_phy_set_prg_regs(phy); + + phy_write(phy, 0x00, DPHY_LOCK_BYP); + phy_write(phy, 0x01, DPHY_TX_RCAL); + phy_write(phy, 0x00, DPHY_AUTO_PD_EN); + phy_write(phy, 0x01, DPHY_RXLPRP); + phy_write(phy, 0x01, DPHY_RXCDRP); + phy_write(phy, 0x25, DPHY_TST); + + /* VCO = REF_CLK * CM / CN * CO */ + if (priv->divider.cm < 16 || priv->divider.cm > 255 || + priv->divider.cn < 1 || priv->divider.cn > 32 || + priv->divider.co < 1 || priv->divider.co > 8) { + dev_err(&phy->dev, "Invalid CM/CN/CO values! (%u/%u/%u)\n", + priv->divider.cm, priv->divider.cn, priv->divider.co); + mutex_unlock(&priv->lock); + return -EINVAL; + } + dev_dbg(&phy->dev, "Using CM:%u CN:%u CO:%u\n", + priv->divider.cm, priv->divider.cn, priv->divider.co); + phy_write(phy, CM(priv->divider.cm), DPHY_CM); + phy_write(phy, CN(priv->divider.cn), DPHY_CN); + phy_write(phy, CO(priv->divider.co), DPHY_CO); + + mutex_unlock(&priv->lock); + + return 0; +} + +static int mixel_mipi_phy_power_on(struct phy *phy) +{ + struct mixel_mipi_phy_priv *priv = phy_get_drvdata(phy); + u32 lock, timeout; + int ret = 0; + + mutex_lock(&priv->lock); + + phy_write(phy, PWR_ON, DPHY_PD_DPHY); + phy_write(phy, PWR_ON, DPHY_PD_PLL); + + timeout = 100; + while (!(lock = phy_read(phy, DPHY_LOCK))) { + udelay(10); + if (--timeout == 0) { + dev_err(&phy->dev, "Could not get DPHY lock!\n"); + mutex_unlock(&priv->lock); + return -EINVAL; + } + } + + if (priv->have_sc) + ret = mixel_mipi_phy_enable(phy, 1); + + mutex_unlock(&priv->lock); + + return ret; +} + +static int mixel_mipi_phy_power_off(struct phy *phy) +{ + struct mixel_mipi_phy_priv *priv = phy_get_drvdata(phy); + int ret = 0; + + mutex_lock(&priv->lock); + + phy_write(phy, PWR_OFF, DPHY_PD_PLL); + phy_write(phy, PWR_OFF, DPHY_PD_DPHY); + + if (priv->have_sc) + ret = mixel_mipi_phy_enable(phy, 0); + + mutex_unlock(&priv->lock); + + return ret; +} + +static const struct phy_ops mixel_mipi_phy_ops = { + .init = mixel_mipi_phy_init, + .power_on = mixel_mipi_phy_power_on, + .power_off = mixel_mipi_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct devtype imx8qm_dev = { .have_sc = true }; +static struct devtype imx8qxp_dev = { .have_sc = true }; + +static const struct of_device_id mixel_mipi_phy_of_match[] = { + { .compatible = "mixel,imx8qm-mipi-dsi-phy", .data = &imx8qm_dev }, + { .compatible = "mixel,imx8qxp-mipi-dsi-phy", .data = &imx8qxp_dev }, + {} +}; +MODULE_DEVICE_TABLE(of, mixel_mipi_phy_of_match); + +static int mixel_mipi_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + const struct of_device_id *of_id = + of_match_device(mixel_mipi_phy_of_match, dev); + const struct devtype *devtype = of_id->data; + struct phy_provider *phy_provider; + struct mixel_mipi_phy_priv *priv; + struct resource *res; + struct phy *phy; + u32 phy_id = 0; + + if (!np) + return -ENODEV; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + priv->base = devm_ioremap(dev, res->start, SZ_256); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + priv->have_sc = devtype->have_sc; + + phy_id = of_alias_get_id(np, "dsi_phy"); + if (phy_id < 0) { + dev_err(dev, "No dsi_phy alias found!"); + return phy_id; + } + + priv->mipi_id = phy_id?SC_R_MIPI_1:SC_R_MIPI_0; + + priv->dev = dev; + + mutex_init(&priv->lock); + dev_set_drvdata(dev, priv); + + phy = devm_phy_create(dev, np, &mixel_mipi_phy_ops); + if (IS_ERR(phy)) { + dev_err(dev, "Failed to create phy\n"); + return PTR_ERR(phy); + } + phy_set_drvdata(phy, priv); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static struct platform_driver mixel_mipi_phy_driver = { + .probe = mixel_mipi_phy_probe, + .driver = { + .name = "mixel-mipi-dsi-phy", + .of_match_table = mixel_mipi_phy_of_match, + } +}; +module_platform_driver(mixel_mipi_phy_driver); + +MODULE_AUTHOR("NXP Semiconductor"); +MODULE_DESCRIPTION("Mixel MIPI-DSI PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/phy/phy-mixel-mipi-dsi.h b/include/linux/phy/phy-mixel-mipi-dsi.h new file mode 100644 index 000000000000..f94190361f76 --- /dev/null +++ b/include/linux/phy/phy-mixel-mipi-dsi.h @@ -0,0 +1,33 @@ +/* + * Copyright 2017 NXP + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#ifndef PHY_MIXEL_MIPI_DSI_H_ +#define PHY_MIXEL_MIPI_DSI_H_ + +#include "phy.h" + +#if IS_ENABLED(CONFIG_PHY_MIXEL_MIPI_DSI) +int mixel_phy_mipi_set_phy_speed(struct phy *phy, + unsigned long bit_clk, + unsigned long ref_clk); +#else +int mixel_phy_mipi_set_phy_speed(struct phy *phy, + unsigned long bit_clk, + unsigned long ref_clk) +{ + return -ENODEV; +} +#endif + +#endif /* PHY_MIXEL_MIPI_DSI_H_ */