summaryrefslogtreecommitdiff
path: root/drivers/phy/phy-mixel-mipi-dsi.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/phy/phy-mixel-mipi-dsi.c')
-rw-r--r--drivers/phy/phy-mixel-mipi-dsi.c538
1 files changed, 538 insertions, 0 deletions
diff --git a/drivers/phy/phy-mixel-mipi-dsi.c b/drivers/phy/phy-mixel-mipi-dsi.c
new file mode 100644
index 000000000000..2966d4bf03e0
--- /dev/null
+++ b/drivers/phy/phy-mixel-mipi-dsi.c
@@ -0,0 +1,538 @@
+/*
+ * Copyright 2018 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 <linux/delay.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/phy/phy-mixel-mipi-dsi.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <soc/imx8/sc/sci.h>
+
+#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 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 devtype {
+ bool have_sc;
+ u8 reg_tx_rcal;
+ u8 reg_auto_pd_en;
+ u8 reg_rxlprp;
+ u8 reg_rxcdrp;
+ u8 reg_rxhs_settle;
+ u8 reg_bypass_pll;
+};
+
+struct mixel_mipi_phy_priv {
+ struct device *dev;
+ void __iomem *base;
+ const struct devtype *plat_data;
+ sc_rsrc_t mipi_id;
+ struct pll_divider divider;
+ struct mutex lock;
+ unsigned long data_rate;
+};
+
+
+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,
+ bool best_match)
+{
+ 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 (best_match && numerator < 16)
+ numerator = div_rate / 1000;
+
+ if (best_match && numerator > 255) {
+ while (numerator > 255 && denominator > 1) {
+ numerator = DIV_ROUND_UP(numerator, 2);
+ denominator = denominator >> 1;
+ }
+ }
+
+ if (numerator < 16 || numerator > 255)
+ return -EINVAL;
+
+ if (best_match)
+ 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);
+ unsigned int hs_reg;
+
+ /* MC_PRG_HS_PREPARE = 1.0 * Ttxescape if DPHY_MC_PRG_HS_PREPARE = 0
+ *
+ * MC_PRG_HS_PREPARE = 1.5 * Ttxescape if DPHY_MC_PRG_HS_PREPARE = 1
+ *
+ * Assume Ftxescape is 18-20 MHz with DPHY_MC_PRG_HS_PREPARE = 0,
+ * this gives 55-50 ns.
+ * The specification is 38 to 95 ns.
+ */
+ phy_write(phy, 0x00, DPHY_MC_PRG_HS_PREPARE);
+
+ /* PRG_HS_PREPARE
+ * for PRG_HS_PREPARE = 00, THS-PREPARE = 1 * TxClkEsc Period
+ * PRG_HS_PREPARE = 01, THS-PREPARE = 1.5 * TxClkEsc Period
+ * PRG_HS_PREPARE = 10, THS-PREPARE = 2 * TxClkEsc Period
+ * PRG_HS_PREPARE = 11, THS-PREPARE = 2.5 * TxClkEsc Period
+ *
+ * The specification for THS-PREPARE is
+ * Min (40ns + 4*UI)
+ * Max 85ns +6*UI
+ */
+ if (priv->data_rate <= MBPS(61))
+ phy_write(phy, 0x03, DPHY_M_PRG_HS_PREPARE);
+ else if (priv->data_rate <= MBPS(90))
+ phy_write(phy, 0x02, DPHY_M_PRG_HS_PREPARE);
+ else if (priv->data_rate <= MBPS(500))
+ phy_write(phy, 0x01, DPHY_M_PRG_HS_PREPARE);
+ else
+ phy_write(phy, 0x00, DPHY_M_PRG_HS_PREPARE);
+
+ /* MC_PRG_HS_ZERO
+ *
+ * T-CLK-ZERO = ( MC_PRG_HS_ZERO + 3) * (TxByteClkHS Period)
+ *
+ * The minimum specification for THS-PREPARE is 262 ns.
+ *
+ */
+ hs_reg =
+ /* simplified equation y = .034x - 2.5
+ *
+ * This a linear interpolation of the values from the
+ * PHY user guide
+ */
+ (34 * (priv->data_rate/1000000) - 2500) / 1000;
+
+ if (hs_reg < 1)
+ hs_reg = 1;
+ phy_write(phy, hs_reg, DPHY_MC_PRG_HS_ZERO);
+
+ /* M_PRG_HS_ZERO
+ *
+ * TT-HS-ZERO =(M_PRG_HS_ZERO + 6) * (TxByteClkHS Period)
+ *
+ * The minimum specification for THS-ZERO 105ns + 6*UI.
+ *
+ */
+ hs_reg =
+ /* simplified equation y = .0144x - 4.75
+ *
+ * This a linear interpolation of the values from the
+ * PHY user guide
+ */
+
+ (144 * (priv->data_rate/1000000) - 47500) / 10000;
+
+ if (hs_reg < 1)
+ hs_reg = 1;
+ phy_write(phy, hs_reg, DPHY_M_PRG_HS_ZERO);
+
+ /* MC_PRG_HS_TRAIL and M_PRG_HS_TRAIL
+ *
+ * THS-TRAIL =(PRG_HS_TRAIL) * (TxByteClkHS Period)
+ *
+ * The specification for THS-TRAIL is
+ * Min (60ns + 4*UI)
+ * Typical (82.5ns + 8*UI)
+ * Max (105ns + 12*UI)
+ *
+ */
+
+ hs_reg =
+ /* simplified equation y = .0103x + 1
+ *
+ * This a linear interpolation of the values from the
+ * PHY user guide
+ */
+ (103 * (priv->data_rate/1000000) + 10000) / 10000;
+
+ if (hs_reg > 15)
+ hs_reg = 15;
+ if (hs_reg < 1)
+ hs_reg = 1;
+
+ phy_write(phy, hs_reg, DPHY_MC_PRG_HS_TRAIL);
+ phy_write(phy, hs_reg, DPHY_M_PRG_HS_TRAIL);
+
+ /* M_PRG_RXHS_SETTLE */
+ if (priv->plat_data->reg_rxhs_settle == 0xFF)
+ return;
+ if (priv->data_rate < MBPS(80))
+ phy_write(phy, 0x0d, priv->plat_data->reg_rxhs_settle);
+ else if (priv->data_rate < MBPS(90))
+ phy_write(phy, 0x0c, priv->plat_data->reg_rxhs_settle);
+ else if (priv->data_rate < MBPS(125))
+ phy_write(phy, 0x0b, priv->plat_data->reg_rxhs_settle);
+ else if (priv->data_rate < MBPS(150))
+ phy_write(phy, 0x0a, priv->plat_data->reg_rxhs_settle);
+ else if (priv->data_rate < MBPS(225))
+ phy_write(phy, 0x09, priv->plat_data->reg_rxhs_settle);
+ else if (priv->data_rate < MBPS(500))
+ phy_write(phy, 0x08, priv->plat_data->reg_rxhs_settle);
+ else
+ phy_write(phy, 0x07, priv->plat_data->reg_rxhs_settle);
+
+}
+
+static int mixel_mipi_phy_init(struct phy *phy)
+{
+ struct mixel_mipi_phy_priv *priv = dev_get_drvdata(phy->dev.parent);
+
+ mutex_lock(&priv->lock);
+
+ phy_write(phy, PWR_OFF, DPHY_PD_PLL);
+ phy_write(phy, PWR_OFF, DPHY_PD_DPHY);
+
+ mixel_phy_set_prg_regs(phy);
+
+ phy_write(phy, 0x00, DPHY_LOCK_BYP);
+ if (priv->plat_data->reg_tx_rcal != 0xFF)
+ phy_write(phy, 0x01, priv->plat_data->reg_tx_rcal);
+ if (priv->plat_data->reg_auto_pd_en != 0xFF)
+ phy_write(phy, 0x00, priv->plat_data->reg_auto_pd_en);
+ if (priv->plat_data->reg_rxlprp != 0xFF)
+ phy_write(phy, 0x02, priv->plat_data->reg_rxlprp);
+ if (priv->plat_data->reg_rxcdrp != 0xFF)
+ phy_write(phy, 0x02, priv->plat_data->reg_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_exit(struct phy *phy)
+{
+ phy_write(phy, 0, DPHY_CM);
+ phy_write(phy, 0, DPHY_CN);
+ phy_write(phy, 0, DPHY_CO);
+
+ 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_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");
+ phy_write(phy, PWR_OFF, DPHY_PD_PLL);
+ mutex_unlock(&priv->lock);
+ return -EINVAL;
+ }
+ }
+ dev_dbg(&phy->dev, "DPHY lock acquired after %d tries\n",
+ (100 - timeout));
+
+ phy_write(phy, PWR_ON, DPHY_PD_DPHY);
+
+ if (priv->plat_data->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->plat_data->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,
+ .exit = mixel_mipi_phy_exit,
+ .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,
+ .reg_tx_rcal = 0xFF,
+ .reg_auto_pd_en = 0x38,
+ .reg_rxlprp = 0x3c,
+ .reg_rxcdrp = 0x40,
+ .reg_rxhs_settle = 0x44,
+ .reg_bypass_pll = 0xFF,
+};
+static struct devtype imx8qxp_dev = {
+ .have_sc = true,
+ .reg_tx_rcal = 0xFF,
+ .reg_auto_pd_en = 0x38,
+ .reg_rxlprp = 0x3c,
+ .reg_rxcdrp = 0x40,
+ .reg_rxhs_settle = 0x44,
+ .reg_bypass_pll = 0xFF,
+};
+static struct devtype imx8mq_dev = {
+ .have_sc = false,
+ .reg_tx_rcal = 0x38,
+ .reg_auto_pd_en = 0x3c,
+ .reg_rxlprp = 0x40,
+ .reg_rxcdrp = 0x44,
+ .reg_rxhs_settle = 0x48,
+ .reg_bypass_pll = 0x4c,
+};
+
+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 },
+ { .compatible = "mixel,imx8mq-mipi-dsi-phy", .data = &imx8mq_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);
+ struct phy_provider *phy_provider;
+ struct mixel_mipi_phy_priv *priv;
+ struct resource *res;
+ struct phy *phy;
+ int 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->plat_data = of_id->data;
+
+ 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");