cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

phy-fsl-imx8-mipi-dphy.c (19177B)


      1// SPDX-License-Identifier: GPL-2.0+
      2/*
      3 * Copyright 2017,2018 NXP
      4 * Copyright 2019 Purism SPC
      5 */
      6
      7#include <linux/bitfield.h>
      8#include <linux/clk.h>
      9#include <linux/clk-provider.h>
     10#include <linux/delay.h>
     11#include <linux/firmware/imx/ipc.h>
     12#include <linux/firmware/imx/svc/misc.h>
     13#include <linux/io.h>
     14#include <linux/kernel.h>
     15#include <linux/mfd/syscon.h>
     16#include <linux/module.h>
     17#include <linux/of.h>
     18#include <linux/of_platform.h>
     19#include <linux/phy/phy.h>
     20#include <linux/platform_device.h>
     21#include <linux/regmap.h>
     22#include <dt-bindings/firmware/imx/rsrc.h>
     23
     24/* Control and Status Registers(CSR) */
     25#define PHY_CTRL			0x00
     26#define  CCM_MASK			GENMASK(7, 5)
     27#define  CCM(n)				FIELD_PREP(CCM_MASK, (n))
     28#define  CCM_1_2V			0x5
     29#define  CA_MASK			GENMASK(4, 2)
     30#define  CA_3_51MA			0x4
     31#define  CA(n)				FIELD_PREP(CA_MASK, (n))
     32#define  RFB				BIT(1)
     33#define  LVDS_EN			BIT(0)
     34
     35/* DPHY registers */
     36#define DPHY_PD_DPHY			0x00
     37#define DPHY_M_PRG_HS_PREPARE		0x04
     38#define DPHY_MC_PRG_HS_PREPARE		0x08
     39#define DPHY_M_PRG_HS_ZERO		0x0c
     40#define DPHY_MC_PRG_HS_ZERO		0x10
     41#define DPHY_M_PRG_HS_TRAIL		0x14
     42#define DPHY_MC_PRG_HS_TRAIL		0x18
     43#define DPHY_PD_PLL			0x1c
     44#define DPHY_TST			0x20
     45#define DPHY_CN				0x24
     46#define DPHY_CM				0x28
     47#define DPHY_CO				0x2c
     48#define DPHY_LOCK			0x30
     49#define DPHY_LOCK_BYP			0x34
     50#define DPHY_REG_BYPASS_PLL		0x4C
     51
     52#define MBPS(x) ((x) * 1000000)
     53
     54#define DATA_RATE_MAX_SPEED MBPS(1500)
     55#define DATA_RATE_MIN_SPEED MBPS(80)
     56
     57#define PLL_LOCK_SLEEP 10
     58#define PLL_LOCK_TIMEOUT 1000
     59
     60#define CN_BUF	0xcb7a89c0
     61#define CO_BUF	0x63
     62#define CM(x)	(				  \
     63		((x) <	32) ? 0xe0 | ((x) - 16) : \
     64		((x) <	64) ? 0xc0 | ((x) - 32) : \
     65		((x) < 128) ? 0x80 | ((x) - 64) : \
     66		((x) - 128))
     67#define CN(x)	(((x) == 1) ? 0x1f : (((CN_BUF) >> ((x) - 1)) & 0x1f))
     68#define CO(x)	((CO_BUF) >> (8 - (x)) & 0x03)
     69
     70/* PHY power on is active low */
     71#define PWR_ON	0
     72#define PWR_OFF	1
     73
     74#define MIN_VCO_FREQ 640000000
     75#define MAX_VCO_FREQ 1500000000
     76
     77#define MIN_LVDS_REFCLK_FREQ 24000000
     78#define MAX_LVDS_REFCLK_FREQ 150000000
     79
     80enum mixel_dphy_devtype {
     81	MIXEL_IMX8MQ,
     82	MIXEL_IMX8QXP,
     83};
     84
     85struct mixel_dphy_devdata {
     86	u8 reg_tx_rcal;
     87	u8 reg_auto_pd_en;
     88	u8 reg_rxlprp;
     89	u8 reg_rxcdrp;
     90	u8 reg_rxhs_settle;
     91	bool is_combo;	/* MIPI DPHY and LVDS PHY combo */
     92};
     93
     94static const struct mixel_dphy_devdata mixel_dphy_devdata[] = {
     95	[MIXEL_IMX8MQ] = {
     96		.reg_tx_rcal = 0x38,
     97		.reg_auto_pd_en = 0x3c,
     98		.reg_rxlprp = 0x40,
     99		.reg_rxcdrp = 0x44,
    100		.reg_rxhs_settle = 0x48,
    101		.is_combo = false,
    102	},
    103	[MIXEL_IMX8QXP] = {
    104		.is_combo = true,
    105	},
    106};
    107
    108struct mixel_dphy_cfg {
    109	/* DPHY PLL parameters */
    110	u32 cm;
    111	u32 cn;
    112	u32 co;
    113	/* DPHY register values */
    114	u8 mc_prg_hs_prepare;
    115	u8 m_prg_hs_prepare;
    116	u8 mc_prg_hs_zero;
    117	u8 m_prg_hs_zero;
    118	u8 mc_prg_hs_trail;
    119	u8 m_prg_hs_trail;
    120	u8 rxhs_settle;
    121};
    122
    123struct mixel_dphy_priv {
    124	struct mixel_dphy_cfg cfg;
    125	struct regmap *regmap;
    126	struct regmap *lvds_regmap;
    127	struct clk *phy_ref_clk;
    128	const struct mixel_dphy_devdata *devdata;
    129	struct imx_sc_ipc *ipc_handle;
    130	bool is_slave;
    131	int id;
    132};
    133
    134static const struct regmap_config mixel_dphy_regmap_config = {
    135	.reg_bits = 8,
    136	.val_bits = 32,
    137	.reg_stride = 4,
    138	.max_register = DPHY_REG_BYPASS_PLL,
    139	.name = "mipi-dphy",
    140};
    141
    142static int phy_write(struct phy *phy, u32 value, unsigned int reg)
    143{
    144	struct mixel_dphy_priv *priv = phy_get_drvdata(phy);
    145	int ret;
    146
    147	ret = regmap_write(priv->regmap, reg, value);
    148	if (ret < 0)
    149		dev_err(&phy->dev, "Failed to write DPHY reg %d: %d\n", reg,
    150			ret);
    151	return ret;
    152}
    153
    154/*
    155 * Find a ratio close to the desired one using continued fraction
    156 * approximation ending either at exact match or maximum allowed
    157 * nominator, denominator.
    158 */
    159static void get_best_ratio(u32 *pnum, u32 *pdenom, u32 max_n, u32 max_d)
    160{
    161	u32 a = *pnum;
    162	u32 b = *pdenom;
    163	u32 c;
    164	u32 n[] = {0, 1};
    165	u32 d[] = {1, 0};
    166	u32 whole;
    167	unsigned int i = 1;
    168
    169	while (b) {
    170		i ^= 1;
    171		whole = a / b;
    172		n[i] += (n[i ^ 1] * whole);
    173		d[i] += (d[i ^ 1] * whole);
    174		if ((n[i] > max_n) || (d[i] > max_d)) {
    175			i ^= 1;
    176			break;
    177		}
    178		c = a - (b * whole);
    179		a = b;
    180		b = c;
    181	}
    182	*pnum = n[i];
    183	*pdenom = d[i];
    184}
    185
    186static int mixel_dphy_config_from_opts(struct phy *phy,
    187	       struct phy_configure_opts_mipi_dphy *dphy_opts,
    188	       struct mixel_dphy_cfg *cfg)
    189{
    190	struct mixel_dphy_priv *priv = dev_get_drvdata(phy->dev.parent);
    191	unsigned long ref_clk = clk_get_rate(priv->phy_ref_clk);
    192	u32 lp_t, numerator, denominator;
    193	unsigned long long tmp;
    194	u32 n;
    195	int i;
    196
    197	if (dphy_opts->hs_clk_rate > DATA_RATE_MAX_SPEED ||
    198	    dphy_opts->hs_clk_rate < DATA_RATE_MIN_SPEED)
    199		return -EINVAL;
    200
    201	numerator = dphy_opts->hs_clk_rate;
    202	denominator = ref_clk;
    203	get_best_ratio(&numerator, &denominator, 255, 256);
    204	if (!numerator || !denominator) {
    205		dev_err(&phy->dev, "Invalid %d/%d for %ld/%ld\n",
    206			numerator, denominator,
    207			dphy_opts->hs_clk_rate, ref_clk);
    208		return -EINVAL;
    209	}
    210
    211	while ((numerator < 16) && (denominator <= 128)) {
    212		numerator <<= 1;
    213		denominator <<= 1;
    214	}
    215	/*
    216	 * CM ranges between 16 and 255
    217	 * CN ranges between 1 and 32
    218	 * CO is power of 2: 1, 2, 4, 8
    219	 */
    220	i = __ffs(denominator);
    221	if (i > 3)
    222		i = 3;
    223	cfg->cn = denominator >> i;
    224	cfg->co = 1 << i;
    225	cfg->cm = numerator;
    226
    227	if (cfg->cm < 16 || cfg->cm > 255 ||
    228	    cfg->cn < 1 || cfg->cn > 32 ||
    229	    cfg->co < 1 || cfg->co > 8) {
    230		dev_err(&phy->dev, "Invalid CM/CN/CO values: %u/%u/%u\n",
    231			cfg->cm, cfg->cn, cfg->co);
    232		dev_err(&phy->dev, "for hs_clk/ref_clk=%ld/%ld ~ %d/%d\n",
    233			dphy_opts->hs_clk_rate, ref_clk,
    234			numerator, denominator);
    235		return -EINVAL;
    236	}
    237
    238	dev_dbg(&phy->dev, "hs_clk/ref_clk=%ld/%ld ~ %d/%d\n",
    239		dphy_opts->hs_clk_rate, ref_clk, numerator, denominator);
    240
    241	/* LP clock period */
    242	tmp = 1000000000000LL;
    243	do_div(tmp, dphy_opts->lp_clk_rate); /* ps */
    244	if (tmp > ULONG_MAX)
    245		return -EINVAL;
    246
    247	lp_t = tmp;
    248	dev_dbg(&phy->dev, "LP clock %lu, period: %u ps\n",
    249		dphy_opts->lp_clk_rate, lp_t);
    250
    251	/* hs_prepare: in lp clock periods */
    252	if (2 * dphy_opts->hs_prepare > 5 * lp_t) {
    253		dev_err(&phy->dev,
    254			"hs_prepare (%u) > 2.5 * lp clock period (%u)\n",
    255			dphy_opts->hs_prepare, lp_t);
    256		return -EINVAL;
    257	}
    258	/* 00: lp_t, 01: 1.5 * lp_t, 10: 2 * lp_t, 11: 2.5 * lp_t */
    259	if (dphy_opts->hs_prepare < lp_t) {
    260		n = 0;
    261	} else {
    262		tmp = 2 * (dphy_opts->hs_prepare - lp_t);
    263		do_div(tmp, lp_t);
    264		n = tmp;
    265	}
    266	cfg->m_prg_hs_prepare = n;
    267
    268	/* clk_prepare: in lp clock periods */
    269	if (2 * dphy_opts->clk_prepare > 3 * lp_t) {
    270		dev_err(&phy->dev,
    271			"clk_prepare (%u) > 1.5 * lp clock period (%u)\n",
    272			dphy_opts->clk_prepare, lp_t);
    273		return -EINVAL;
    274	}
    275	/* 00: lp_t, 01: 1.5 * lp_t */
    276	cfg->mc_prg_hs_prepare = dphy_opts->clk_prepare > lp_t ? 1 : 0;
    277
    278	/* hs_zero: formula from NXP BSP */
    279	n = (144 * (dphy_opts->hs_clk_rate / 1000000) - 47500) / 10000;
    280	cfg->m_prg_hs_zero = n < 1 ? 1 : n;
    281
    282	/* clk_zero: formula from NXP BSP */
    283	n = (34 * (dphy_opts->hs_clk_rate / 1000000) - 2500) / 1000;
    284	cfg->mc_prg_hs_zero = n < 1 ? 1 : n;
    285
    286	/* clk_trail, hs_trail: formula from NXP BSP */
    287	n = (103 * (dphy_opts->hs_clk_rate / 1000000) + 10000) / 10000;
    288	if (n > 15)
    289		n = 15;
    290	if (n < 1)
    291		n = 1;
    292	cfg->m_prg_hs_trail = n;
    293	cfg->mc_prg_hs_trail = n;
    294
    295	/* rxhs_settle: formula from NXP BSP */
    296	if (dphy_opts->hs_clk_rate < MBPS(80))
    297		cfg->rxhs_settle = 0x0d;
    298	else if (dphy_opts->hs_clk_rate < MBPS(90))
    299		cfg->rxhs_settle = 0x0c;
    300	else if (dphy_opts->hs_clk_rate < MBPS(125))
    301		cfg->rxhs_settle = 0x0b;
    302	else if (dphy_opts->hs_clk_rate < MBPS(150))
    303		cfg->rxhs_settle = 0x0a;
    304	else if (dphy_opts->hs_clk_rate < MBPS(225))
    305		cfg->rxhs_settle = 0x09;
    306	else if (dphy_opts->hs_clk_rate < MBPS(500))
    307		cfg->rxhs_settle = 0x08;
    308	else
    309		cfg->rxhs_settle = 0x07;
    310
    311	dev_dbg(&phy->dev, "phy_config: %u %u %u %u %u %u %u\n",
    312		cfg->m_prg_hs_prepare, cfg->mc_prg_hs_prepare,
    313		cfg->m_prg_hs_zero, cfg->mc_prg_hs_zero,
    314		cfg->m_prg_hs_trail, cfg->mc_prg_hs_trail,
    315		cfg->rxhs_settle);
    316
    317	return 0;
    318}
    319
    320static void mixel_phy_set_hs_timings(struct phy *phy)
    321{
    322	struct mixel_dphy_priv *priv = phy_get_drvdata(phy);
    323
    324	phy_write(phy, priv->cfg.m_prg_hs_prepare, DPHY_M_PRG_HS_PREPARE);
    325	phy_write(phy, priv->cfg.mc_prg_hs_prepare, DPHY_MC_PRG_HS_PREPARE);
    326	phy_write(phy, priv->cfg.m_prg_hs_zero, DPHY_M_PRG_HS_ZERO);
    327	phy_write(phy, priv->cfg.mc_prg_hs_zero, DPHY_MC_PRG_HS_ZERO);
    328	phy_write(phy, priv->cfg.m_prg_hs_trail, DPHY_M_PRG_HS_TRAIL);
    329	phy_write(phy, priv->cfg.mc_prg_hs_trail, DPHY_MC_PRG_HS_TRAIL);
    330	phy_write(phy, priv->cfg.rxhs_settle, priv->devdata->reg_rxhs_settle);
    331}
    332
    333static int mixel_dphy_set_pll_params(struct phy *phy)
    334{
    335	struct mixel_dphy_priv *priv = dev_get_drvdata(phy->dev.parent);
    336
    337	if (priv->cfg.cm < 16 || priv->cfg.cm > 255 ||
    338	    priv->cfg.cn < 1 || priv->cfg.cn > 32 ||
    339	    priv->cfg.co < 1 || priv->cfg.co > 8) {
    340		dev_err(&phy->dev, "Invalid CM/CN/CO values! (%u/%u/%u)\n",
    341			priv->cfg.cm, priv->cfg.cn, priv->cfg.co);
    342		return -EINVAL;
    343	}
    344	dev_dbg(&phy->dev, "Using CM:%u CN:%u CO:%u\n",
    345		priv->cfg.cm, priv->cfg.cn, priv->cfg.co);
    346	phy_write(phy, CM(priv->cfg.cm), DPHY_CM);
    347	phy_write(phy, CN(priv->cfg.cn), DPHY_CN);
    348	phy_write(phy, CO(priv->cfg.co), DPHY_CO);
    349	return 0;
    350}
    351
    352static int
    353mixel_dphy_configure_mipi_dphy(struct phy *phy, union phy_configure_opts *opts)
    354{
    355	struct mixel_dphy_priv *priv = phy_get_drvdata(phy);
    356	struct mixel_dphy_cfg cfg = { 0 };
    357	int ret;
    358
    359	ret = mixel_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg);
    360	if (ret)
    361		return ret;
    362
    363	/* Update the configuration */
    364	memcpy(&priv->cfg, &cfg, sizeof(struct mixel_dphy_cfg));
    365
    366	phy_write(phy, 0x00, DPHY_LOCK_BYP);
    367	phy_write(phy, 0x01, priv->devdata->reg_tx_rcal);
    368	phy_write(phy, 0x00, priv->devdata->reg_auto_pd_en);
    369	phy_write(phy, 0x02, priv->devdata->reg_rxlprp);
    370	phy_write(phy, 0x02, priv->devdata->reg_rxcdrp);
    371	phy_write(phy, 0x25, DPHY_TST);
    372
    373	mixel_phy_set_hs_timings(phy);
    374	ret = mixel_dphy_set_pll_params(phy);
    375	if (ret < 0)
    376		return ret;
    377
    378	return 0;
    379}
    380
    381static int
    382mixel_dphy_configure_lvds_phy(struct phy *phy, union phy_configure_opts *opts)
    383{
    384	struct mixel_dphy_priv *priv = phy_get_drvdata(phy);
    385	struct phy_configure_opts_lvds *lvds_opts = &opts->lvds;
    386	unsigned long data_rate;
    387	unsigned long fvco;
    388	u32 rsc;
    389	u32 co;
    390	int ret;
    391
    392	priv->is_slave = lvds_opts->is_slave;
    393
    394	/* LVDS interface pins */
    395	regmap_write(priv->lvds_regmap, PHY_CTRL,
    396		     CCM(CCM_1_2V) | CA(CA_3_51MA) | RFB);
    397
    398	/* enable MODE8 only for slave LVDS PHY */
    399	rsc = priv->id ? IMX_SC_R_MIPI_1 : IMX_SC_R_MIPI_0;
    400	ret = imx_sc_misc_set_control(priv->ipc_handle, rsc, IMX_SC_C_DUAL_MODE,
    401				      lvds_opts->is_slave);
    402	if (ret) {
    403		dev_err(&phy->dev, "Failed to configure MODE8: %d\n", ret);
    404		return ret;
    405	}
    406
    407	/*
    408	 * Choose an appropriate divider ratio to meet the requirement of
    409	 * PLL VCO frequency range.
    410	 *
    411	 *  -----  640MHz ~ 1500MHz   ------------      ---------------
    412	 * | VCO | ----------------> | CO divider | -> | LVDS data rate|
    413	 *  -----       FVCO          ------------      ---------------
    414	 *                            1/2/4/8 div     7 * differential_clk_rate
    415	 */
    416	data_rate = 7 * lvds_opts->differential_clk_rate;
    417	for (co = 1; co <= 8; co *= 2) {
    418		fvco = data_rate * co;
    419
    420		if (fvco >= MIN_VCO_FREQ)
    421			break;
    422	}
    423
    424	if (fvco < MIN_VCO_FREQ || fvco > MAX_VCO_FREQ) {
    425		dev_err(&phy->dev, "VCO frequency %lu is out of range\n", fvco);
    426		return -ERANGE;
    427	}
    428
    429	/*
    430	 * CO is configurable, while CN and CM are not,
    431	 * as fixed ratios 1 and 7 are applied respectively.
    432	 */
    433	phy_write(phy, __ffs(co), DPHY_CO);
    434
    435	/* set reference clock rate */
    436	clk_set_rate(priv->phy_ref_clk, lvds_opts->differential_clk_rate);
    437
    438	return ret;
    439}
    440
    441static int mixel_dphy_configure(struct phy *phy, union phy_configure_opts *opts)
    442{
    443	if (!opts) {
    444		dev_err(&phy->dev, "No configuration options\n");
    445		return -EINVAL;
    446	}
    447
    448	if (phy->attrs.mode == PHY_MODE_MIPI_DPHY)
    449		return mixel_dphy_configure_mipi_dphy(phy, opts);
    450	else if (phy->attrs.mode == PHY_MODE_LVDS)
    451		return mixel_dphy_configure_lvds_phy(phy, opts);
    452
    453	dev_err(&phy->dev,
    454		"Failed to configure PHY with invalid PHY mode: %d\n", phy->attrs.mode);
    455
    456	return -EINVAL;
    457}
    458
    459static int
    460mixel_dphy_validate_lvds_phy(struct phy *phy, union phy_configure_opts *opts)
    461{
    462	struct phy_configure_opts_lvds *lvds_cfg = &opts->lvds;
    463
    464	if (lvds_cfg->bits_per_lane_and_dclk_cycle != 7) {
    465		dev_err(&phy->dev, "Invalid bits per LVDS data lane: %u\n",
    466			lvds_cfg->bits_per_lane_and_dclk_cycle);
    467		return -EINVAL;
    468	}
    469
    470	if (lvds_cfg->lanes != 4) {
    471		dev_err(&phy->dev, "Invalid LVDS data lanes: %u\n", lvds_cfg->lanes);
    472		return -EINVAL;
    473	}
    474
    475	if (lvds_cfg->differential_clk_rate < MIN_LVDS_REFCLK_FREQ ||
    476	    lvds_cfg->differential_clk_rate > MAX_LVDS_REFCLK_FREQ) {
    477		dev_err(&phy->dev,
    478			"Invalid LVDS differential clock rate: %lu\n",
    479			lvds_cfg->differential_clk_rate);
    480		return -EINVAL;
    481	}
    482
    483	return 0;
    484}
    485
    486static int mixel_dphy_validate(struct phy *phy, enum phy_mode mode, int submode,
    487			       union phy_configure_opts *opts)
    488{
    489	if (mode == PHY_MODE_MIPI_DPHY) {
    490		struct mixel_dphy_cfg mipi_dphy_cfg = { 0 };
    491
    492		return mixel_dphy_config_from_opts(phy, &opts->mipi_dphy,
    493						   &mipi_dphy_cfg);
    494	} else if (mode == PHY_MODE_LVDS) {
    495		return mixel_dphy_validate_lvds_phy(phy, opts);
    496	}
    497
    498	dev_err(&phy->dev,
    499		"Failed to validate PHY with invalid PHY mode: %d\n", mode);
    500	return -EINVAL;
    501}
    502
    503static int mixel_dphy_init(struct phy *phy)
    504{
    505	phy_write(phy, PWR_OFF, DPHY_PD_PLL);
    506	phy_write(phy, PWR_OFF, DPHY_PD_DPHY);
    507
    508	return 0;
    509}
    510
    511static int mixel_dphy_exit(struct phy *phy)
    512{
    513	phy_write(phy, 0, DPHY_CM);
    514	phy_write(phy, 0, DPHY_CN);
    515	phy_write(phy, 0, DPHY_CO);
    516
    517	return 0;
    518}
    519
    520static int mixel_dphy_power_on_mipi_dphy(struct phy *phy)
    521{
    522	struct mixel_dphy_priv *priv = phy_get_drvdata(phy);
    523	u32 locked;
    524	int ret;
    525
    526	phy_write(phy, PWR_ON, DPHY_PD_PLL);
    527	ret = regmap_read_poll_timeout(priv->regmap, DPHY_LOCK, locked,
    528				       locked, PLL_LOCK_SLEEP,
    529				       PLL_LOCK_TIMEOUT);
    530	if (ret < 0) {
    531		dev_err(&phy->dev, "Could not get DPHY lock (%d)!\n", ret);
    532		return ret;
    533	}
    534	phy_write(phy, PWR_ON, DPHY_PD_DPHY);
    535
    536	return 0;
    537}
    538
    539static int mixel_dphy_power_on_lvds_phy(struct phy *phy)
    540{
    541	struct mixel_dphy_priv *priv = phy_get_drvdata(phy);
    542	u32 locked;
    543	int ret;
    544
    545	regmap_update_bits(priv->lvds_regmap, PHY_CTRL, LVDS_EN, LVDS_EN);
    546
    547	phy_write(phy, PWR_ON, DPHY_PD_DPHY);
    548	phy_write(phy, PWR_ON, DPHY_PD_PLL);
    549
    550	/* do not wait for slave LVDS PHY being locked */
    551	if (priv->is_slave)
    552		return 0;
    553
    554	ret = regmap_read_poll_timeout(priv->regmap, DPHY_LOCK, locked,
    555				       locked, PLL_LOCK_SLEEP,
    556				       PLL_LOCK_TIMEOUT);
    557	if (ret < 0) {
    558		dev_err(&phy->dev, "Could not get LVDS PHY lock (%d)!\n", ret);
    559		return ret;
    560	}
    561
    562	return 0;
    563}
    564
    565static int mixel_dphy_power_on(struct phy *phy)
    566{
    567	struct mixel_dphy_priv *priv = phy_get_drvdata(phy);
    568	int ret;
    569
    570	ret = clk_prepare_enable(priv->phy_ref_clk);
    571	if (ret < 0)
    572		return ret;
    573
    574	if (phy->attrs.mode == PHY_MODE_MIPI_DPHY) {
    575		ret = mixel_dphy_power_on_mipi_dphy(phy);
    576	} else if (phy->attrs.mode == PHY_MODE_LVDS) {
    577		ret = mixel_dphy_power_on_lvds_phy(phy);
    578	} else {
    579		dev_err(&phy->dev,
    580			"Failed to power on PHY with invalid PHY mode: %d\n",
    581							phy->attrs.mode);
    582		ret = -EINVAL;
    583	}
    584
    585	if (ret)
    586		goto clock_disable;
    587
    588	return 0;
    589clock_disable:
    590	clk_disable_unprepare(priv->phy_ref_clk);
    591	return ret;
    592}
    593
    594static int mixel_dphy_power_off(struct phy *phy)
    595{
    596	struct mixel_dphy_priv *priv = phy_get_drvdata(phy);
    597
    598	phy_write(phy, PWR_OFF, DPHY_PD_PLL);
    599	phy_write(phy, PWR_OFF, DPHY_PD_DPHY);
    600
    601	if (phy->attrs.mode == PHY_MODE_LVDS)
    602		regmap_update_bits(priv->lvds_regmap, PHY_CTRL, LVDS_EN, 0);
    603
    604	clk_disable_unprepare(priv->phy_ref_clk);
    605
    606	return 0;
    607}
    608
    609static int mixel_dphy_set_mode(struct phy *phy, enum phy_mode mode, int submode)
    610{
    611	struct mixel_dphy_priv *priv = phy_get_drvdata(phy);
    612	int ret;
    613
    614	if (priv->devdata->is_combo && mode != PHY_MODE_LVDS) {
    615		dev_err(&phy->dev, "Failed to set PHY mode for combo PHY\n");
    616		return -EINVAL;
    617	}
    618
    619	if (!priv->devdata->is_combo && mode != PHY_MODE_MIPI_DPHY) {
    620		dev_err(&phy->dev, "Failed to set PHY mode to MIPI DPHY\n");
    621		return -EINVAL;
    622	}
    623
    624	if (priv->devdata->is_combo) {
    625		u32 rsc = priv->id ? IMX_SC_R_MIPI_1 : IMX_SC_R_MIPI_0;
    626
    627		ret = imx_sc_misc_set_control(priv->ipc_handle,
    628					      rsc, IMX_SC_C_MODE,
    629					      mode == PHY_MODE_LVDS);
    630		if (ret) {
    631			dev_err(&phy->dev,
    632				"Failed to set PHY mode via SCU ipc: %d\n", ret);
    633			return ret;
    634		}
    635	}
    636
    637	return 0;
    638}
    639
    640static const struct phy_ops mixel_dphy_phy_ops = {
    641	.init = mixel_dphy_init,
    642	.exit = mixel_dphy_exit,
    643	.power_on = mixel_dphy_power_on,
    644	.power_off = mixel_dphy_power_off,
    645	.set_mode = mixel_dphy_set_mode,
    646	.configure = mixel_dphy_configure,
    647	.validate = mixel_dphy_validate,
    648	.owner = THIS_MODULE,
    649};
    650
    651static const struct of_device_id mixel_dphy_of_match[] = {
    652	{ .compatible = "fsl,imx8mq-mipi-dphy",
    653	  .data = &mixel_dphy_devdata[MIXEL_IMX8MQ] },
    654	{ .compatible = "fsl,imx8qxp-mipi-dphy",
    655	  .data = &mixel_dphy_devdata[MIXEL_IMX8QXP] },
    656	{ /* sentinel */ },
    657};
    658MODULE_DEVICE_TABLE(of, mixel_dphy_of_match);
    659
    660static int mixel_dphy_probe(struct platform_device *pdev)
    661{
    662	struct device *dev = &pdev->dev;
    663	struct device_node *np = dev->of_node;
    664	struct phy_provider *phy_provider;
    665	struct mixel_dphy_priv *priv;
    666	struct phy *phy;
    667	void __iomem *base;
    668	int ret;
    669
    670	if (!np)
    671		return -ENODEV;
    672
    673	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
    674	if (!priv)
    675		return -ENOMEM;
    676
    677	priv->devdata = of_device_get_match_data(&pdev->dev);
    678	if (!priv->devdata)
    679		return -EINVAL;
    680
    681	base = devm_platform_ioremap_resource(pdev, 0);
    682	if (IS_ERR(base))
    683		return PTR_ERR(base);
    684
    685	priv->regmap = devm_regmap_init_mmio(&pdev->dev, base,
    686					     &mixel_dphy_regmap_config);
    687	if (IS_ERR(priv->regmap)) {
    688		dev_err(dev, "Couldn't create the DPHY regmap\n");
    689		return PTR_ERR(priv->regmap);
    690	}
    691
    692	priv->phy_ref_clk = devm_clk_get(&pdev->dev, "phy_ref");
    693	if (IS_ERR(priv->phy_ref_clk)) {
    694		dev_err(dev, "No phy_ref clock found\n");
    695		return PTR_ERR(priv->phy_ref_clk);
    696	}
    697	dev_dbg(dev, "phy_ref clock rate: %lu\n",
    698		clk_get_rate(priv->phy_ref_clk));
    699
    700	if (priv->devdata->is_combo) {
    701		priv->lvds_regmap =
    702			syscon_regmap_lookup_by_phandle(np, "fsl,syscon");
    703		if (IS_ERR(priv->lvds_regmap)) {
    704			ret = PTR_ERR(priv->lvds_regmap);
    705			dev_err_probe(dev, ret, "Failed to get LVDS regmap\n");
    706			return ret;
    707		}
    708
    709		priv->id = of_alias_get_id(np, "mipi_dphy");
    710		if (priv->id < 0) {
    711			dev_err(dev, "Failed to get phy node alias id: %d\n",
    712				priv->id);
    713			return priv->id;
    714		}
    715
    716		ret = imx_scu_get_handle(&priv->ipc_handle);
    717		if (ret) {
    718			dev_err_probe(dev, ret,
    719				      "Failed to get SCU ipc handle\n");
    720			return ret;
    721		}
    722	}
    723
    724	dev_set_drvdata(dev, priv);
    725
    726	phy = devm_phy_create(dev, np, &mixel_dphy_phy_ops);
    727	if (IS_ERR(phy)) {
    728		dev_err(dev, "Failed to create phy %ld\n", PTR_ERR(phy));
    729		return PTR_ERR(phy);
    730	}
    731	phy_set_drvdata(phy, priv);
    732
    733	phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
    734
    735	return PTR_ERR_OR_ZERO(phy_provider);
    736}
    737
    738static struct platform_driver mixel_dphy_driver = {
    739	.probe	= mixel_dphy_probe,
    740	.driver = {
    741		.name = "mixel-mipi-dphy",
    742		.of_match_table	= mixel_dphy_of_match,
    743	}
    744};
    745module_platform_driver(mixel_dphy_driver);
    746
    747MODULE_AUTHOR("NXP Semiconductor");
    748MODULE_DESCRIPTION("Mixel MIPI-DSI PHY driver");
    749MODULE_LICENSE("GPL");