18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright 2017,2018 NXP
48c2ecf20Sopenharmony_ci * Copyright 2019 Purism SPC
58c2ecf20Sopenharmony_ci */
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ci#include <linux/clk.h>
88c2ecf20Sopenharmony_ci#include <linux/clk-provider.h>
98c2ecf20Sopenharmony_ci#include <linux/delay.h>
108c2ecf20Sopenharmony_ci#include <linux/io.h>
118c2ecf20Sopenharmony_ci#include <linux/kernel.h>
128c2ecf20Sopenharmony_ci#include <linux/module.h>
138c2ecf20Sopenharmony_ci#include <linux/of.h>
148c2ecf20Sopenharmony_ci#include <linux/of_platform.h>
158c2ecf20Sopenharmony_ci#include <linux/phy/phy.h>
168c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
178c2ecf20Sopenharmony_ci#include <linux/regmap.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci/* DPHY registers */
208c2ecf20Sopenharmony_ci#define DPHY_PD_DPHY			0x00
218c2ecf20Sopenharmony_ci#define DPHY_M_PRG_HS_PREPARE		0x04
228c2ecf20Sopenharmony_ci#define DPHY_MC_PRG_HS_PREPARE		0x08
238c2ecf20Sopenharmony_ci#define DPHY_M_PRG_HS_ZERO		0x0c
248c2ecf20Sopenharmony_ci#define DPHY_MC_PRG_HS_ZERO		0x10
258c2ecf20Sopenharmony_ci#define DPHY_M_PRG_HS_TRAIL		0x14
268c2ecf20Sopenharmony_ci#define DPHY_MC_PRG_HS_TRAIL		0x18
278c2ecf20Sopenharmony_ci#define DPHY_PD_PLL			0x1c
288c2ecf20Sopenharmony_ci#define DPHY_TST			0x20
298c2ecf20Sopenharmony_ci#define DPHY_CN				0x24
308c2ecf20Sopenharmony_ci#define DPHY_CM				0x28
318c2ecf20Sopenharmony_ci#define DPHY_CO				0x2c
328c2ecf20Sopenharmony_ci#define DPHY_LOCK			0x30
338c2ecf20Sopenharmony_ci#define DPHY_LOCK_BYP			0x34
348c2ecf20Sopenharmony_ci#define DPHY_REG_BYPASS_PLL		0x4C
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci#define MBPS(x) ((x) * 1000000)
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci#define DATA_RATE_MAX_SPEED MBPS(1500)
398c2ecf20Sopenharmony_ci#define DATA_RATE_MIN_SPEED MBPS(80)
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci#define PLL_LOCK_SLEEP 10
428c2ecf20Sopenharmony_ci#define PLL_LOCK_TIMEOUT 1000
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci#define CN_BUF	0xcb7a89c0
458c2ecf20Sopenharmony_ci#define CO_BUF	0x63
468c2ecf20Sopenharmony_ci#define CM(x)	(				  \
478c2ecf20Sopenharmony_ci		((x) <	32) ? 0xe0 | ((x) - 16) : \
488c2ecf20Sopenharmony_ci		((x) <	64) ? 0xc0 | ((x) - 32) : \
498c2ecf20Sopenharmony_ci		((x) < 128) ? 0x80 | ((x) - 64) : \
508c2ecf20Sopenharmony_ci		((x) - 128))
518c2ecf20Sopenharmony_ci#define CN(x)	(((x) == 1) ? 0x1f : (((CN_BUF) >> ((x) - 1)) & 0x1f))
528c2ecf20Sopenharmony_ci#define CO(x)	((CO_BUF) >> (8 - (x)) & 0x03)
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci/* PHY power on is active low */
558c2ecf20Sopenharmony_ci#define PWR_ON	0
568c2ecf20Sopenharmony_ci#define PWR_OFF	1
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_cienum mixel_dphy_devtype {
598c2ecf20Sopenharmony_ci	MIXEL_IMX8MQ,
608c2ecf20Sopenharmony_ci};
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_cistruct mixel_dphy_devdata {
638c2ecf20Sopenharmony_ci	u8 reg_tx_rcal;
648c2ecf20Sopenharmony_ci	u8 reg_auto_pd_en;
658c2ecf20Sopenharmony_ci	u8 reg_rxlprp;
668c2ecf20Sopenharmony_ci	u8 reg_rxcdrp;
678c2ecf20Sopenharmony_ci	u8 reg_rxhs_settle;
688c2ecf20Sopenharmony_ci};
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_cistatic const struct mixel_dphy_devdata mixel_dphy_devdata[] = {
718c2ecf20Sopenharmony_ci	[MIXEL_IMX8MQ] = {
728c2ecf20Sopenharmony_ci		.reg_tx_rcal = 0x38,
738c2ecf20Sopenharmony_ci		.reg_auto_pd_en = 0x3c,
748c2ecf20Sopenharmony_ci		.reg_rxlprp = 0x40,
758c2ecf20Sopenharmony_ci		.reg_rxcdrp = 0x44,
768c2ecf20Sopenharmony_ci		.reg_rxhs_settle = 0x48,
778c2ecf20Sopenharmony_ci	},
788c2ecf20Sopenharmony_ci};
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_cistruct mixel_dphy_cfg {
818c2ecf20Sopenharmony_ci	/* DPHY PLL parameters */
828c2ecf20Sopenharmony_ci	u32 cm;
838c2ecf20Sopenharmony_ci	u32 cn;
848c2ecf20Sopenharmony_ci	u32 co;
858c2ecf20Sopenharmony_ci	/* DPHY register values */
868c2ecf20Sopenharmony_ci	u8 mc_prg_hs_prepare;
878c2ecf20Sopenharmony_ci	u8 m_prg_hs_prepare;
888c2ecf20Sopenharmony_ci	u8 mc_prg_hs_zero;
898c2ecf20Sopenharmony_ci	u8 m_prg_hs_zero;
908c2ecf20Sopenharmony_ci	u8 mc_prg_hs_trail;
918c2ecf20Sopenharmony_ci	u8 m_prg_hs_trail;
928c2ecf20Sopenharmony_ci	u8 rxhs_settle;
938c2ecf20Sopenharmony_ci};
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_cistruct mixel_dphy_priv {
968c2ecf20Sopenharmony_ci	struct mixel_dphy_cfg cfg;
978c2ecf20Sopenharmony_ci	struct regmap *regmap;
988c2ecf20Sopenharmony_ci	struct clk *phy_ref_clk;
998c2ecf20Sopenharmony_ci	const struct mixel_dphy_devdata *devdata;
1008c2ecf20Sopenharmony_ci};
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_cistatic const struct regmap_config mixel_dphy_regmap_config = {
1038c2ecf20Sopenharmony_ci	.reg_bits = 8,
1048c2ecf20Sopenharmony_ci	.val_bits = 32,
1058c2ecf20Sopenharmony_ci	.reg_stride = 4,
1068c2ecf20Sopenharmony_ci	.max_register = DPHY_REG_BYPASS_PLL,
1078c2ecf20Sopenharmony_ci	.name = "mipi-dphy",
1088c2ecf20Sopenharmony_ci};
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_cistatic int phy_write(struct phy *phy, u32 value, unsigned int reg)
1118c2ecf20Sopenharmony_ci{
1128c2ecf20Sopenharmony_ci	struct mixel_dphy_priv *priv = phy_get_drvdata(phy);
1138c2ecf20Sopenharmony_ci	int ret;
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	ret = regmap_write(priv->regmap, reg, value);
1168c2ecf20Sopenharmony_ci	if (ret < 0)
1178c2ecf20Sopenharmony_ci		dev_err(&phy->dev, "Failed to write DPHY reg %d: %d\n", reg,
1188c2ecf20Sopenharmony_ci			ret);
1198c2ecf20Sopenharmony_ci	return ret;
1208c2ecf20Sopenharmony_ci}
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci/*
1238c2ecf20Sopenharmony_ci * Find a ratio close to the desired one using continued fraction
1248c2ecf20Sopenharmony_ci * approximation ending either at exact match or maximum allowed
1258c2ecf20Sopenharmony_ci * nominator, denominator.
1268c2ecf20Sopenharmony_ci */
1278c2ecf20Sopenharmony_cistatic void get_best_ratio(u32 *pnum, u32 *pdenom, u32 max_n, u32 max_d)
1288c2ecf20Sopenharmony_ci{
1298c2ecf20Sopenharmony_ci	u32 a = *pnum;
1308c2ecf20Sopenharmony_ci	u32 b = *pdenom;
1318c2ecf20Sopenharmony_ci	u32 c;
1328c2ecf20Sopenharmony_ci	u32 n[] = {0, 1};
1338c2ecf20Sopenharmony_ci	u32 d[] = {1, 0};
1348c2ecf20Sopenharmony_ci	u32 whole;
1358c2ecf20Sopenharmony_ci	unsigned int i = 1;
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	while (b) {
1388c2ecf20Sopenharmony_ci		i ^= 1;
1398c2ecf20Sopenharmony_ci		whole = a / b;
1408c2ecf20Sopenharmony_ci		n[i] += (n[i ^ 1] * whole);
1418c2ecf20Sopenharmony_ci		d[i] += (d[i ^ 1] * whole);
1428c2ecf20Sopenharmony_ci		if ((n[i] > max_n) || (d[i] > max_d)) {
1438c2ecf20Sopenharmony_ci			i ^= 1;
1448c2ecf20Sopenharmony_ci			break;
1458c2ecf20Sopenharmony_ci		}
1468c2ecf20Sopenharmony_ci		c = a - (b * whole);
1478c2ecf20Sopenharmony_ci		a = b;
1488c2ecf20Sopenharmony_ci		b = c;
1498c2ecf20Sopenharmony_ci	}
1508c2ecf20Sopenharmony_ci	*pnum = n[i];
1518c2ecf20Sopenharmony_ci	*pdenom = d[i];
1528c2ecf20Sopenharmony_ci}
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_cistatic int mixel_dphy_config_from_opts(struct phy *phy,
1558c2ecf20Sopenharmony_ci	       struct phy_configure_opts_mipi_dphy *dphy_opts,
1568c2ecf20Sopenharmony_ci	       struct mixel_dphy_cfg *cfg)
1578c2ecf20Sopenharmony_ci{
1588c2ecf20Sopenharmony_ci	struct mixel_dphy_priv *priv = dev_get_drvdata(phy->dev.parent);
1598c2ecf20Sopenharmony_ci	unsigned long ref_clk = clk_get_rate(priv->phy_ref_clk);
1608c2ecf20Sopenharmony_ci	u32 lp_t, numerator, denominator;
1618c2ecf20Sopenharmony_ci	unsigned long long tmp;
1628c2ecf20Sopenharmony_ci	u32 n;
1638c2ecf20Sopenharmony_ci	int i;
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	if (dphy_opts->hs_clk_rate > DATA_RATE_MAX_SPEED ||
1668c2ecf20Sopenharmony_ci	    dphy_opts->hs_clk_rate < DATA_RATE_MIN_SPEED)
1678c2ecf20Sopenharmony_ci		return -EINVAL;
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	numerator = dphy_opts->hs_clk_rate;
1708c2ecf20Sopenharmony_ci	denominator = ref_clk;
1718c2ecf20Sopenharmony_ci	get_best_ratio(&numerator, &denominator, 255, 256);
1728c2ecf20Sopenharmony_ci	if (!numerator || !denominator) {
1738c2ecf20Sopenharmony_ci		dev_err(&phy->dev, "Invalid %d/%d for %ld/%ld\n",
1748c2ecf20Sopenharmony_ci			numerator, denominator,
1758c2ecf20Sopenharmony_ci			dphy_opts->hs_clk_rate, ref_clk);
1768c2ecf20Sopenharmony_ci		return -EINVAL;
1778c2ecf20Sopenharmony_ci	}
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci	while ((numerator < 16) && (denominator <= 128)) {
1808c2ecf20Sopenharmony_ci		numerator <<= 1;
1818c2ecf20Sopenharmony_ci		denominator <<= 1;
1828c2ecf20Sopenharmony_ci	}
1838c2ecf20Sopenharmony_ci	/*
1848c2ecf20Sopenharmony_ci	 * CM ranges between 16 and 255
1858c2ecf20Sopenharmony_ci	 * CN ranges between 1 and 32
1868c2ecf20Sopenharmony_ci	 * CO is power of 2: 1, 2, 4, 8
1878c2ecf20Sopenharmony_ci	 */
1888c2ecf20Sopenharmony_ci	i = __ffs(denominator);
1898c2ecf20Sopenharmony_ci	if (i > 3)
1908c2ecf20Sopenharmony_ci		i = 3;
1918c2ecf20Sopenharmony_ci	cfg->cn = denominator >> i;
1928c2ecf20Sopenharmony_ci	cfg->co = 1 << i;
1938c2ecf20Sopenharmony_ci	cfg->cm = numerator;
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci	if (cfg->cm < 16 || cfg->cm > 255 ||
1968c2ecf20Sopenharmony_ci	    cfg->cn < 1 || cfg->cn > 32 ||
1978c2ecf20Sopenharmony_ci	    cfg->co < 1 || cfg->co > 8) {
1988c2ecf20Sopenharmony_ci		dev_err(&phy->dev, "Invalid CM/CN/CO values: %u/%u/%u\n",
1998c2ecf20Sopenharmony_ci			cfg->cm, cfg->cn, cfg->co);
2008c2ecf20Sopenharmony_ci		dev_err(&phy->dev, "for hs_clk/ref_clk=%ld/%ld ~ %d/%d\n",
2018c2ecf20Sopenharmony_ci			dphy_opts->hs_clk_rate, ref_clk,
2028c2ecf20Sopenharmony_ci			numerator, denominator);
2038c2ecf20Sopenharmony_ci		return -EINVAL;
2048c2ecf20Sopenharmony_ci	}
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci	dev_dbg(&phy->dev, "hs_clk/ref_clk=%ld/%ld ~ %d/%d\n",
2078c2ecf20Sopenharmony_ci		dphy_opts->hs_clk_rate, ref_clk, numerator, denominator);
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	/* LP clock period */
2108c2ecf20Sopenharmony_ci	tmp = 1000000000000LL;
2118c2ecf20Sopenharmony_ci	do_div(tmp, dphy_opts->lp_clk_rate); /* ps */
2128c2ecf20Sopenharmony_ci	if (tmp > ULONG_MAX)
2138c2ecf20Sopenharmony_ci		return -EINVAL;
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_ci	lp_t = tmp;
2168c2ecf20Sopenharmony_ci	dev_dbg(&phy->dev, "LP clock %lu, period: %u ps\n",
2178c2ecf20Sopenharmony_ci		dphy_opts->lp_clk_rate, lp_t);
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	/* hs_prepare: in lp clock periods */
2208c2ecf20Sopenharmony_ci	if (2 * dphy_opts->hs_prepare > 5 * lp_t) {
2218c2ecf20Sopenharmony_ci		dev_err(&phy->dev,
2228c2ecf20Sopenharmony_ci			"hs_prepare (%u) > 2.5 * lp clock period (%u)\n",
2238c2ecf20Sopenharmony_ci			dphy_opts->hs_prepare, lp_t);
2248c2ecf20Sopenharmony_ci		return -EINVAL;
2258c2ecf20Sopenharmony_ci	}
2268c2ecf20Sopenharmony_ci	/* 00: lp_t, 01: 1.5 * lp_t, 10: 2 * lp_t, 11: 2.5 * lp_t */
2278c2ecf20Sopenharmony_ci	if (dphy_opts->hs_prepare < lp_t) {
2288c2ecf20Sopenharmony_ci		n = 0;
2298c2ecf20Sopenharmony_ci	} else {
2308c2ecf20Sopenharmony_ci		tmp = 2 * (dphy_opts->hs_prepare - lp_t);
2318c2ecf20Sopenharmony_ci		do_div(tmp, lp_t);
2328c2ecf20Sopenharmony_ci		n = tmp;
2338c2ecf20Sopenharmony_ci	}
2348c2ecf20Sopenharmony_ci	cfg->m_prg_hs_prepare = n;
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ci	/* clk_prepare: in lp clock periods */
2378c2ecf20Sopenharmony_ci	if (2 * dphy_opts->clk_prepare > 3 * lp_t) {
2388c2ecf20Sopenharmony_ci		dev_err(&phy->dev,
2398c2ecf20Sopenharmony_ci			"clk_prepare (%u) > 1.5 * lp clock period (%u)\n",
2408c2ecf20Sopenharmony_ci			dphy_opts->clk_prepare, lp_t);
2418c2ecf20Sopenharmony_ci		return -EINVAL;
2428c2ecf20Sopenharmony_ci	}
2438c2ecf20Sopenharmony_ci	/* 00: lp_t, 01: 1.5 * lp_t */
2448c2ecf20Sopenharmony_ci	cfg->mc_prg_hs_prepare = dphy_opts->clk_prepare > lp_t ? 1 : 0;
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ci	/* hs_zero: formula from NXP BSP */
2478c2ecf20Sopenharmony_ci	n = (144 * (dphy_opts->hs_clk_rate / 1000000) - 47500) / 10000;
2488c2ecf20Sopenharmony_ci	cfg->m_prg_hs_zero = n < 1 ? 1 : n;
2498c2ecf20Sopenharmony_ci
2508c2ecf20Sopenharmony_ci	/* clk_zero: formula from NXP BSP */
2518c2ecf20Sopenharmony_ci	n = (34 * (dphy_opts->hs_clk_rate / 1000000) - 2500) / 1000;
2528c2ecf20Sopenharmony_ci	cfg->mc_prg_hs_zero = n < 1 ? 1 : n;
2538c2ecf20Sopenharmony_ci
2548c2ecf20Sopenharmony_ci	/* clk_trail, hs_trail: formula from NXP BSP */
2558c2ecf20Sopenharmony_ci	n = (103 * (dphy_opts->hs_clk_rate / 1000000) + 10000) / 10000;
2568c2ecf20Sopenharmony_ci	if (n > 15)
2578c2ecf20Sopenharmony_ci		n = 15;
2588c2ecf20Sopenharmony_ci	if (n < 1)
2598c2ecf20Sopenharmony_ci		n = 1;
2608c2ecf20Sopenharmony_ci	cfg->m_prg_hs_trail = n;
2618c2ecf20Sopenharmony_ci	cfg->mc_prg_hs_trail = n;
2628c2ecf20Sopenharmony_ci
2638c2ecf20Sopenharmony_ci	/* rxhs_settle: formula from NXP BSP */
2648c2ecf20Sopenharmony_ci	if (dphy_opts->hs_clk_rate < MBPS(80))
2658c2ecf20Sopenharmony_ci		cfg->rxhs_settle = 0x0d;
2668c2ecf20Sopenharmony_ci	else if (dphy_opts->hs_clk_rate < MBPS(90))
2678c2ecf20Sopenharmony_ci		cfg->rxhs_settle = 0x0c;
2688c2ecf20Sopenharmony_ci	else if (dphy_opts->hs_clk_rate < MBPS(125))
2698c2ecf20Sopenharmony_ci		cfg->rxhs_settle = 0x0b;
2708c2ecf20Sopenharmony_ci	else if (dphy_opts->hs_clk_rate < MBPS(150))
2718c2ecf20Sopenharmony_ci		cfg->rxhs_settle = 0x0a;
2728c2ecf20Sopenharmony_ci	else if (dphy_opts->hs_clk_rate < MBPS(225))
2738c2ecf20Sopenharmony_ci		cfg->rxhs_settle = 0x09;
2748c2ecf20Sopenharmony_ci	else if (dphy_opts->hs_clk_rate < MBPS(500))
2758c2ecf20Sopenharmony_ci		cfg->rxhs_settle = 0x08;
2768c2ecf20Sopenharmony_ci	else
2778c2ecf20Sopenharmony_ci		cfg->rxhs_settle = 0x07;
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_ci	dev_dbg(&phy->dev, "phy_config: %u %u %u %u %u %u %u\n",
2808c2ecf20Sopenharmony_ci		cfg->m_prg_hs_prepare, cfg->mc_prg_hs_prepare,
2818c2ecf20Sopenharmony_ci		cfg->m_prg_hs_zero, cfg->mc_prg_hs_zero,
2828c2ecf20Sopenharmony_ci		cfg->m_prg_hs_trail, cfg->mc_prg_hs_trail,
2838c2ecf20Sopenharmony_ci		cfg->rxhs_settle);
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_ci	return 0;
2868c2ecf20Sopenharmony_ci}
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_cistatic void mixel_phy_set_hs_timings(struct phy *phy)
2898c2ecf20Sopenharmony_ci{
2908c2ecf20Sopenharmony_ci	struct mixel_dphy_priv *priv = phy_get_drvdata(phy);
2918c2ecf20Sopenharmony_ci
2928c2ecf20Sopenharmony_ci	phy_write(phy, priv->cfg.m_prg_hs_prepare, DPHY_M_PRG_HS_PREPARE);
2938c2ecf20Sopenharmony_ci	phy_write(phy, priv->cfg.mc_prg_hs_prepare, DPHY_MC_PRG_HS_PREPARE);
2948c2ecf20Sopenharmony_ci	phy_write(phy, priv->cfg.m_prg_hs_zero, DPHY_M_PRG_HS_ZERO);
2958c2ecf20Sopenharmony_ci	phy_write(phy, priv->cfg.mc_prg_hs_zero, DPHY_MC_PRG_HS_ZERO);
2968c2ecf20Sopenharmony_ci	phy_write(phy, priv->cfg.m_prg_hs_trail, DPHY_M_PRG_HS_TRAIL);
2978c2ecf20Sopenharmony_ci	phy_write(phy, priv->cfg.mc_prg_hs_trail, DPHY_MC_PRG_HS_TRAIL);
2988c2ecf20Sopenharmony_ci	phy_write(phy, priv->cfg.rxhs_settle, priv->devdata->reg_rxhs_settle);
2998c2ecf20Sopenharmony_ci}
3008c2ecf20Sopenharmony_ci
3018c2ecf20Sopenharmony_cistatic int mixel_dphy_set_pll_params(struct phy *phy)
3028c2ecf20Sopenharmony_ci{
3038c2ecf20Sopenharmony_ci	struct mixel_dphy_priv *priv = dev_get_drvdata(phy->dev.parent);
3048c2ecf20Sopenharmony_ci
3058c2ecf20Sopenharmony_ci	if (priv->cfg.cm < 16 || priv->cfg.cm > 255 ||
3068c2ecf20Sopenharmony_ci	    priv->cfg.cn < 1 || priv->cfg.cn > 32 ||
3078c2ecf20Sopenharmony_ci	    priv->cfg.co < 1 || priv->cfg.co > 8) {
3088c2ecf20Sopenharmony_ci		dev_err(&phy->dev, "Invalid CM/CN/CO values! (%u/%u/%u)\n",
3098c2ecf20Sopenharmony_ci			priv->cfg.cm, priv->cfg.cn, priv->cfg.co);
3108c2ecf20Sopenharmony_ci		return -EINVAL;
3118c2ecf20Sopenharmony_ci	}
3128c2ecf20Sopenharmony_ci	dev_dbg(&phy->dev, "Using CM:%u CN:%u CO:%u\n",
3138c2ecf20Sopenharmony_ci		priv->cfg.cm, priv->cfg.cn, priv->cfg.co);
3148c2ecf20Sopenharmony_ci	phy_write(phy, CM(priv->cfg.cm), DPHY_CM);
3158c2ecf20Sopenharmony_ci	phy_write(phy, CN(priv->cfg.cn), DPHY_CN);
3168c2ecf20Sopenharmony_ci	phy_write(phy, CO(priv->cfg.co), DPHY_CO);
3178c2ecf20Sopenharmony_ci	return 0;
3188c2ecf20Sopenharmony_ci}
3198c2ecf20Sopenharmony_ci
3208c2ecf20Sopenharmony_cistatic int mixel_dphy_configure(struct phy *phy, union phy_configure_opts *opts)
3218c2ecf20Sopenharmony_ci{
3228c2ecf20Sopenharmony_ci	struct mixel_dphy_priv *priv = phy_get_drvdata(phy);
3238c2ecf20Sopenharmony_ci	struct mixel_dphy_cfg cfg = { 0 };
3248c2ecf20Sopenharmony_ci	int ret;
3258c2ecf20Sopenharmony_ci
3268c2ecf20Sopenharmony_ci	ret = mixel_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg);
3278c2ecf20Sopenharmony_ci	if (ret)
3288c2ecf20Sopenharmony_ci		return ret;
3298c2ecf20Sopenharmony_ci
3308c2ecf20Sopenharmony_ci	/* Update the configuration */
3318c2ecf20Sopenharmony_ci	memcpy(&priv->cfg, &cfg, sizeof(struct mixel_dphy_cfg));
3328c2ecf20Sopenharmony_ci
3338c2ecf20Sopenharmony_ci	phy_write(phy, 0x00, DPHY_LOCK_BYP);
3348c2ecf20Sopenharmony_ci	phy_write(phy, 0x01, priv->devdata->reg_tx_rcal);
3358c2ecf20Sopenharmony_ci	phy_write(phy, 0x00, priv->devdata->reg_auto_pd_en);
3368c2ecf20Sopenharmony_ci	phy_write(phy, 0x02, priv->devdata->reg_rxlprp);
3378c2ecf20Sopenharmony_ci	phy_write(phy, 0x02, priv->devdata->reg_rxcdrp);
3388c2ecf20Sopenharmony_ci	phy_write(phy, 0x25, DPHY_TST);
3398c2ecf20Sopenharmony_ci
3408c2ecf20Sopenharmony_ci	mixel_phy_set_hs_timings(phy);
3418c2ecf20Sopenharmony_ci	ret = mixel_dphy_set_pll_params(phy);
3428c2ecf20Sopenharmony_ci	if (ret < 0)
3438c2ecf20Sopenharmony_ci		return ret;
3448c2ecf20Sopenharmony_ci
3458c2ecf20Sopenharmony_ci	return 0;
3468c2ecf20Sopenharmony_ci}
3478c2ecf20Sopenharmony_ci
3488c2ecf20Sopenharmony_cistatic int mixel_dphy_validate(struct phy *phy, enum phy_mode mode, int submode,
3498c2ecf20Sopenharmony_ci			       union phy_configure_opts *opts)
3508c2ecf20Sopenharmony_ci{
3518c2ecf20Sopenharmony_ci	struct mixel_dphy_cfg cfg = { 0 };
3528c2ecf20Sopenharmony_ci
3538c2ecf20Sopenharmony_ci	if (mode != PHY_MODE_MIPI_DPHY)
3548c2ecf20Sopenharmony_ci		return -EINVAL;
3558c2ecf20Sopenharmony_ci
3568c2ecf20Sopenharmony_ci	return mixel_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg);
3578c2ecf20Sopenharmony_ci}
3588c2ecf20Sopenharmony_ci
3598c2ecf20Sopenharmony_cistatic int mixel_dphy_init(struct phy *phy)
3608c2ecf20Sopenharmony_ci{
3618c2ecf20Sopenharmony_ci	phy_write(phy, PWR_OFF, DPHY_PD_PLL);
3628c2ecf20Sopenharmony_ci	phy_write(phy, PWR_OFF, DPHY_PD_DPHY);
3638c2ecf20Sopenharmony_ci
3648c2ecf20Sopenharmony_ci	return 0;
3658c2ecf20Sopenharmony_ci}
3668c2ecf20Sopenharmony_ci
3678c2ecf20Sopenharmony_cistatic int mixel_dphy_exit(struct phy *phy)
3688c2ecf20Sopenharmony_ci{
3698c2ecf20Sopenharmony_ci	phy_write(phy, 0, DPHY_CM);
3708c2ecf20Sopenharmony_ci	phy_write(phy, 0, DPHY_CN);
3718c2ecf20Sopenharmony_ci	phy_write(phy, 0, DPHY_CO);
3728c2ecf20Sopenharmony_ci
3738c2ecf20Sopenharmony_ci	return 0;
3748c2ecf20Sopenharmony_ci}
3758c2ecf20Sopenharmony_ci
3768c2ecf20Sopenharmony_cistatic int mixel_dphy_power_on(struct phy *phy)
3778c2ecf20Sopenharmony_ci{
3788c2ecf20Sopenharmony_ci	struct mixel_dphy_priv *priv = phy_get_drvdata(phy);
3798c2ecf20Sopenharmony_ci	u32 locked;
3808c2ecf20Sopenharmony_ci	int ret;
3818c2ecf20Sopenharmony_ci
3828c2ecf20Sopenharmony_ci	ret = clk_prepare_enable(priv->phy_ref_clk);
3838c2ecf20Sopenharmony_ci	if (ret < 0)
3848c2ecf20Sopenharmony_ci		return ret;
3858c2ecf20Sopenharmony_ci
3868c2ecf20Sopenharmony_ci	phy_write(phy, PWR_ON, DPHY_PD_PLL);
3878c2ecf20Sopenharmony_ci	ret = regmap_read_poll_timeout(priv->regmap, DPHY_LOCK, locked,
3888c2ecf20Sopenharmony_ci				       locked, PLL_LOCK_SLEEP,
3898c2ecf20Sopenharmony_ci				       PLL_LOCK_TIMEOUT);
3908c2ecf20Sopenharmony_ci	if (ret < 0) {
3918c2ecf20Sopenharmony_ci		dev_err(&phy->dev, "Could not get DPHY lock (%d)!\n", ret);
3928c2ecf20Sopenharmony_ci		goto clock_disable;
3938c2ecf20Sopenharmony_ci	}
3948c2ecf20Sopenharmony_ci	phy_write(phy, PWR_ON, DPHY_PD_DPHY);
3958c2ecf20Sopenharmony_ci
3968c2ecf20Sopenharmony_ci	return 0;
3978c2ecf20Sopenharmony_ciclock_disable:
3988c2ecf20Sopenharmony_ci	clk_disable_unprepare(priv->phy_ref_clk);
3998c2ecf20Sopenharmony_ci	return ret;
4008c2ecf20Sopenharmony_ci}
4018c2ecf20Sopenharmony_ci
4028c2ecf20Sopenharmony_cistatic int mixel_dphy_power_off(struct phy *phy)
4038c2ecf20Sopenharmony_ci{
4048c2ecf20Sopenharmony_ci	struct mixel_dphy_priv *priv = phy_get_drvdata(phy);
4058c2ecf20Sopenharmony_ci
4068c2ecf20Sopenharmony_ci	phy_write(phy, PWR_OFF, DPHY_PD_PLL);
4078c2ecf20Sopenharmony_ci	phy_write(phy, PWR_OFF, DPHY_PD_DPHY);
4088c2ecf20Sopenharmony_ci
4098c2ecf20Sopenharmony_ci	clk_disable_unprepare(priv->phy_ref_clk);
4108c2ecf20Sopenharmony_ci
4118c2ecf20Sopenharmony_ci	return 0;
4128c2ecf20Sopenharmony_ci}
4138c2ecf20Sopenharmony_ci
4148c2ecf20Sopenharmony_cistatic const struct phy_ops mixel_dphy_phy_ops = {
4158c2ecf20Sopenharmony_ci	.init = mixel_dphy_init,
4168c2ecf20Sopenharmony_ci	.exit = mixel_dphy_exit,
4178c2ecf20Sopenharmony_ci	.power_on = mixel_dphy_power_on,
4188c2ecf20Sopenharmony_ci	.power_off = mixel_dphy_power_off,
4198c2ecf20Sopenharmony_ci	.configure = mixel_dphy_configure,
4208c2ecf20Sopenharmony_ci	.validate = mixel_dphy_validate,
4218c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
4228c2ecf20Sopenharmony_ci};
4238c2ecf20Sopenharmony_ci
4248c2ecf20Sopenharmony_cistatic const struct of_device_id mixel_dphy_of_match[] = {
4258c2ecf20Sopenharmony_ci	{ .compatible = "fsl,imx8mq-mipi-dphy",
4268c2ecf20Sopenharmony_ci	  .data = &mixel_dphy_devdata[MIXEL_IMX8MQ] },
4278c2ecf20Sopenharmony_ci	{ /* sentinel */ },
4288c2ecf20Sopenharmony_ci};
4298c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, mixel_dphy_of_match);
4308c2ecf20Sopenharmony_ci
4318c2ecf20Sopenharmony_cistatic int mixel_dphy_probe(struct platform_device *pdev)
4328c2ecf20Sopenharmony_ci{
4338c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
4348c2ecf20Sopenharmony_ci	struct device_node *np = dev->of_node;
4358c2ecf20Sopenharmony_ci	struct phy_provider *phy_provider;
4368c2ecf20Sopenharmony_ci	struct mixel_dphy_priv *priv;
4378c2ecf20Sopenharmony_ci	struct resource *res;
4388c2ecf20Sopenharmony_ci	struct phy *phy;
4398c2ecf20Sopenharmony_ci	void __iomem *base;
4408c2ecf20Sopenharmony_ci
4418c2ecf20Sopenharmony_ci	if (!np)
4428c2ecf20Sopenharmony_ci		return -ENODEV;
4438c2ecf20Sopenharmony_ci
4448c2ecf20Sopenharmony_ci	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
4458c2ecf20Sopenharmony_ci	if (!priv)
4468c2ecf20Sopenharmony_ci		return -ENOMEM;
4478c2ecf20Sopenharmony_ci
4488c2ecf20Sopenharmony_ci	priv->devdata = of_device_get_match_data(&pdev->dev);
4498c2ecf20Sopenharmony_ci	if (!priv->devdata)
4508c2ecf20Sopenharmony_ci		return -EINVAL;
4518c2ecf20Sopenharmony_ci
4528c2ecf20Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
4538c2ecf20Sopenharmony_ci	base = devm_ioremap_resource(dev, res);
4548c2ecf20Sopenharmony_ci	if (IS_ERR(base))
4558c2ecf20Sopenharmony_ci		return PTR_ERR(base);
4568c2ecf20Sopenharmony_ci
4578c2ecf20Sopenharmony_ci	priv->regmap = devm_regmap_init_mmio(&pdev->dev, base,
4588c2ecf20Sopenharmony_ci					     &mixel_dphy_regmap_config);
4598c2ecf20Sopenharmony_ci	if (IS_ERR(priv->regmap)) {
4608c2ecf20Sopenharmony_ci		dev_err(dev, "Couldn't create the DPHY regmap\n");
4618c2ecf20Sopenharmony_ci		return PTR_ERR(priv->regmap);
4628c2ecf20Sopenharmony_ci	}
4638c2ecf20Sopenharmony_ci
4648c2ecf20Sopenharmony_ci	priv->phy_ref_clk = devm_clk_get(&pdev->dev, "phy_ref");
4658c2ecf20Sopenharmony_ci	if (IS_ERR(priv->phy_ref_clk)) {
4668c2ecf20Sopenharmony_ci		dev_err(dev, "No phy_ref clock found\n");
4678c2ecf20Sopenharmony_ci		return PTR_ERR(priv->phy_ref_clk);
4688c2ecf20Sopenharmony_ci	}
4698c2ecf20Sopenharmony_ci	dev_dbg(dev, "phy_ref clock rate: %lu\n",
4708c2ecf20Sopenharmony_ci		clk_get_rate(priv->phy_ref_clk));
4718c2ecf20Sopenharmony_ci
4728c2ecf20Sopenharmony_ci	dev_set_drvdata(dev, priv);
4738c2ecf20Sopenharmony_ci
4748c2ecf20Sopenharmony_ci	phy = devm_phy_create(dev, np, &mixel_dphy_phy_ops);
4758c2ecf20Sopenharmony_ci	if (IS_ERR(phy)) {
4768c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to create phy %ld\n", PTR_ERR(phy));
4778c2ecf20Sopenharmony_ci		return PTR_ERR(phy);
4788c2ecf20Sopenharmony_ci	}
4798c2ecf20Sopenharmony_ci	phy_set_drvdata(phy, priv);
4808c2ecf20Sopenharmony_ci
4818c2ecf20Sopenharmony_ci	phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
4828c2ecf20Sopenharmony_ci
4838c2ecf20Sopenharmony_ci	return PTR_ERR_OR_ZERO(phy_provider);
4848c2ecf20Sopenharmony_ci}
4858c2ecf20Sopenharmony_ci
4868c2ecf20Sopenharmony_cistatic struct platform_driver mixel_dphy_driver = {
4878c2ecf20Sopenharmony_ci	.probe	= mixel_dphy_probe,
4888c2ecf20Sopenharmony_ci	.driver = {
4898c2ecf20Sopenharmony_ci		.name = "mixel-mipi-dphy",
4908c2ecf20Sopenharmony_ci		.of_match_table	= mixel_dphy_of_match,
4918c2ecf20Sopenharmony_ci	}
4928c2ecf20Sopenharmony_ci};
4938c2ecf20Sopenharmony_cimodule_platform_driver(mixel_dphy_driver);
4948c2ecf20Sopenharmony_ci
4958c2ecf20Sopenharmony_ciMODULE_AUTHOR("NXP Semiconductor");
4968c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Mixel MIPI-DSI PHY driver");
4978c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
498