diff options
Diffstat (limited to 'drivers/phy/phy-mixel-mipi-dsi.c')
-rw-r--r-- | drivers/phy/phy-mixel-mipi-dsi.c | 538 |
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"); |