162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright: 2017-2018 Cadence Design Systems, Inc.
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/bitfield.h>
762306a36Sopenharmony_ci#include <linux/bitops.h>
862306a36Sopenharmony_ci#include <linux/clk.h>
962306a36Sopenharmony_ci#include <linux/io.h>
1062306a36Sopenharmony_ci#include <linux/iopoll.h>
1162306a36Sopenharmony_ci#include <linux/module.h>
1262306a36Sopenharmony_ci#include <linux/of.h>
1362306a36Sopenharmony_ci#include <linux/platform_device.h>
1462306a36Sopenharmony_ci#include <linux/reset.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include <linux/phy/phy.h>
1762306a36Sopenharmony_ci#include <linux/phy/phy-mipi-dphy.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#define REG_WAKEUP_TIME_NS		800
2062306a36Sopenharmony_ci#define DPHY_PLL_RATE_HZ		108000000
2162306a36Sopenharmony_ci#define POLL_TIMEOUT_US			1000
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci/* DPHY registers */
2462306a36Sopenharmony_ci#define DPHY_PMA_CMN(reg)		(reg)
2562306a36Sopenharmony_ci#define DPHY_PMA_LCLK(reg)		(0x100 + (reg))
2662306a36Sopenharmony_ci#define DPHY_PMA_LDATA(lane, reg)	(0x200 + ((lane) * 0x100) + (reg))
2762306a36Sopenharmony_ci#define DPHY_PMA_RCLK(reg)		(0x600 + (reg))
2862306a36Sopenharmony_ci#define DPHY_PMA_RDATA(lane, reg)	(0x700 + ((lane) * 0x100) + (reg))
2962306a36Sopenharmony_ci#define DPHY_PCS(reg)			(0xb00 + (reg))
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci#define DPHY_CMN_SSM			DPHY_PMA_CMN(0x20)
3262306a36Sopenharmony_ci#define DPHY_CMN_SSM_EN			BIT(0)
3362306a36Sopenharmony_ci#define DPHY_CMN_TX_MODE_EN		BIT(9)
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci#define DPHY_CMN_PWM			DPHY_PMA_CMN(0x40)
3662306a36Sopenharmony_ci#define DPHY_CMN_PWM_DIV(x)		((x) << 20)
3762306a36Sopenharmony_ci#define DPHY_CMN_PWM_LOW(x)		((x) << 10)
3862306a36Sopenharmony_ci#define DPHY_CMN_PWM_HIGH(x)		(x)
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci#define DPHY_CMN_FBDIV			DPHY_PMA_CMN(0x4c)
4162306a36Sopenharmony_ci#define DPHY_CMN_FBDIV_VAL(low, high)	(((high) << 11) | ((low) << 22))
4262306a36Sopenharmony_ci#define DPHY_CMN_FBDIV_FROM_REG		(BIT(10) | BIT(21))
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci#define DPHY_CMN_OPIPDIV		DPHY_PMA_CMN(0x50)
4562306a36Sopenharmony_ci#define DPHY_CMN_IPDIV_FROM_REG		BIT(0)
4662306a36Sopenharmony_ci#define DPHY_CMN_IPDIV(x)		((x) << 1)
4762306a36Sopenharmony_ci#define DPHY_CMN_OPDIV_FROM_REG		BIT(6)
4862306a36Sopenharmony_ci#define DPHY_CMN_OPDIV(x)		((x) << 7)
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci#define DPHY_BAND_CFG			DPHY_PCS(0x0)
5162306a36Sopenharmony_ci#define DPHY_BAND_CFG_LEFT_BAND		GENMASK(4, 0)
5262306a36Sopenharmony_ci#define DPHY_BAND_CFG_RIGHT_BAND	GENMASK(9, 5)
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci#define DPHY_PSM_CFG			DPHY_PCS(0x4)
5562306a36Sopenharmony_ci#define DPHY_PSM_CFG_FROM_REG		BIT(0)
5662306a36Sopenharmony_ci#define DPHY_PSM_CLK_DIV(x)		((x) << 1)
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci#define DSI_HBP_FRAME_OVERHEAD		12
5962306a36Sopenharmony_ci#define DSI_HSA_FRAME_OVERHEAD		14
6062306a36Sopenharmony_ci#define DSI_HFP_FRAME_OVERHEAD		6
6162306a36Sopenharmony_ci#define DSI_HSS_VSS_VSE_FRAME_OVERHEAD	4
6262306a36Sopenharmony_ci#define DSI_BLANKING_FRAME_OVERHEAD	6
6362306a36Sopenharmony_ci#define DSI_NULL_FRAME_OVERHEAD		6
6462306a36Sopenharmony_ci#define DSI_EOT_PKT_SIZE		4
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci#define DPHY_TX_J721E_WIZ_PLL_CTRL	0xF04
6762306a36Sopenharmony_ci#define DPHY_TX_J721E_WIZ_STATUS	0xF08
6862306a36Sopenharmony_ci#define DPHY_TX_J721E_WIZ_RST_CTRL	0xF0C
6962306a36Sopenharmony_ci#define DPHY_TX_J721E_WIZ_PSM_FREQ	0xF10
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci#define DPHY_TX_J721E_WIZ_IPDIV		GENMASK(4, 0)
7262306a36Sopenharmony_ci#define DPHY_TX_J721E_WIZ_OPDIV		GENMASK(13, 8)
7362306a36Sopenharmony_ci#define DPHY_TX_J721E_WIZ_FBDIV		GENMASK(25, 16)
7462306a36Sopenharmony_ci#define DPHY_TX_J721E_WIZ_LANE_RSTB	BIT(31)
7562306a36Sopenharmony_ci#define DPHY_TX_WIZ_PLL_LOCK		BIT(31)
7662306a36Sopenharmony_ci#define DPHY_TX_WIZ_O_CMN_READY		BIT(31)
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_cistruct cdns_dphy_cfg {
7962306a36Sopenharmony_ci	u8 pll_ipdiv;
8062306a36Sopenharmony_ci	u8 pll_opdiv;
8162306a36Sopenharmony_ci	u16 pll_fbdiv;
8262306a36Sopenharmony_ci	unsigned int nlanes;
8362306a36Sopenharmony_ci};
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_cienum cdns_dphy_clk_lane_cfg {
8662306a36Sopenharmony_ci	DPHY_CLK_CFG_LEFT_DRIVES_ALL = 0,
8762306a36Sopenharmony_ci	DPHY_CLK_CFG_LEFT_DRIVES_RIGHT = 1,
8862306a36Sopenharmony_ci	DPHY_CLK_CFG_LEFT_DRIVES_LEFT = 2,
8962306a36Sopenharmony_ci	DPHY_CLK_CFG_RIGHT_DRIVES_ALL = 3,
9062306a36Sopenharmony_ci};
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_cistruct cdns_dphy;
9362306a36Sopenharmony_cistruct cdns_dphy_ops {
9462306a36Sopenharmony_ci	int (*probe)(struct cdns_dphy *dphy);
9562306a36Sopenharmony_ci	void (*remove)(struct cdns_dphy *dphy);
9662306a36Sopenharmony_ci	void (*set_psm_div)(struct cdns_dphy *dphy, u8 div);
9762306a36Sopenharmony_ci	void (*set_clk_lane_cfg)(struct cdns_dphy *dphy,
9862306a36Sopenharmony_ci				 enum cdns_dphy_clk_lane_cfg cfg);
9962306a36Sopenharmony_ci	void (*set_pll_cfg)(struct cdns_dphy *dphy,
10062306a36Sopenharmony_ci			    const struct cdns_dphy_cfg *cfg);
10162306a36Sopenharmony_ci	unsigned long (*get_wakeup_time_ns)(struct cdns_dphy *dphy);
10262306a36Sopenharmony_ci};
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_cistruct cdns_dphy {
10562306a36Sopenharmony_ci	struct cdns_dphy_cfg cfg;
10662306a36Sopenharmony_ci	void __iomem *regs;
10762306a36Sopenharmony_ci	struct clk *psm_clk;
10862306a36Sopenharmony_ci	struct clk *pll_ref_clk;
10962306a36Sopenharmony_ci	const struct cdns_dphy_ops *ops;
11062306a36Sopenharmony_ci	struct phy *phy;
11162306a36Sopenharmony_ci};
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci/* Order of bands is important since the index is the band number. */
11462306a36Sopenharmony_cistatic const unsigned int tx_bands[] = {
11562306a36Sopenharmony_ci	80, 100, 120, 160, 200, 240, 320, 390, 450, 510, 560, 640, 690, 770,
11662306a36Sopenharmony_ci	870, 950, 1000, 1200, 1400, 1600, 1800, 2000, 2200, 2500
11762306a36Sopenharmony_ci};
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_cistatic int cdns_dsi_get_dphy_pll_cfg(struct cdns_dphy *dphy,
12062306a36Sopenharmony_ci				     struct cdns_dphy_cfg *cfg,
12162306a36Sopenharmony_ci				     struct phy_configure_opts_mipi_dphy *opts,
12262306a36Sopenharmony_ci				     unsigned int *dsi_hfp_ext)
12362306a36Sopenharmony_ci{
12462306a36Sopenharmony_ci	unsigned long pll_ref_hz = clk_get_rate(dphy->pll_ref_clk);
12562306a36Sopenharmony_ci	u64 dlane_bps;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	memset(cfg, 0, sizeof(*cfg));
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	if (pll_ref_hz < 9600000 || pll_ref_hz >= 150000000)
13062306a36Sopenharmony_ci		return -EINVAL;
13162306a36Sopenharmony_ci	else if (pll_ref_hz < 19200000)
13262306a36Sopenharmony_ci		cfg->pll_ipdiv = 1;
13362306a36Sopenharmony_ci	else if (pll_ref_hz < 38400000)
13462306a36Sopenharmony_ci		cfg->pll_ipdiv = 2;
13562306a36Sopenharmony_ci	else if (pll_ref_hz < 76800000)
13662306a36Sopenharmony_ci		cfg->pll_ipdiv = 4;
13762306a36Sopenharmony_ci	else
13862306a36Sopenharmony_ci		cfg->pll_ipdiv = 8;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	dlane_bps = opts->hs_clk_rate;
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	if (dlane_bps > 2500000000UL || dlane_bps < 160000000UL)
14362306a36Sopenharmony_ci		return -EINVAL;
14462306a36Sopenharmony_ci	else if (dlane_bps >= 1250000000)
14562306a36Sopenharmony_ci		cfg->pll_opdiv = 1;
14662306a36Sopenharmony_ci	else if (dlane_bps >= 630000000)
14762306a36Sopenharmony_ci		cfg->pll_opdiv = 2;
14862306a36Sopenharmony_ci	else if (dlane_bps >= 320000000)
14962306a36Sopenharmony_ci		cfg->pll_opdiv = 4;
15062306a36Sopenharmony_ci	else if (dlane_bps >= 160000000)
15162306a36Sopenharmony_ci		cfg->pll_opdiv = 8;
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	cfg->pll_fbdiv = DIV_ROUND_UP_ULL(dlane_bps * 2 * cfg->pll_opdiv *
15462306a36Sopenharmony_ci					  cfg->pll_ipdiv,
15562306a36Sopenharmony_ci					  pll_ref_hz);
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	return 0;
15862306a36Sopenharmony_ci}
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_cistatic int cdns_dphy_setup_psm(struct cdns_dphy *dphy)
16162306a36Sopenharmony_ci{
16262306a36Sopenharmony_ci	unsigned long psm_clk_hz = clk_get_rate(dphy->psm_clk);
16362306a36Sopenharmony_ci	unsigned long psm_div;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	if (!psm_clk_hz || psm_clk_hz > 100000000)
16662306a36Sopenharmony_ci		return -EINVAL;
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	psm_div = DIV_ROUND_CLOSEST(psm_clk_hz, 1000000);
16962306a36Sopenharmony_ci	if (dphy->ops->set_psm_div)
17062306a36Sopenharmony_ci		dphy->ops->set_psm_div(dphy, psm_div);
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	return 0;
17362306a36Sopenharmony_ci}
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_cistatic void cdns_dphy_set_clk_lane_cfg(struct cdns_dphy *dphy,
17662306a36Sopenharmony_ci				       enum cdns_dphy_clk_lane_cfg cfg)
17762306a36Sopenharmony_ci{
17862306a36Sopenharmony_ci	if (dphy->ops->set_clk_lane_cfg)
17962306a36Sopenharmony_ci		dphy->ops->set_clk_lane_cfg(dphy, cfg);
18062306a36Sopenharmony_ci}
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_cistatic void cdns_dphy_set_pll_cfg(struct cdns_dphy *dphy,
18362306a36Sopenharmony_ci				  const struct cdns_dphy_cfg *cfg)
18462306a36Sopenharmony_ci{
18562306a36Sopenharmony_ci	if (dphy->ops->set_pll_cfg)
18662306a36Sopenharmony_ci		dphy->ops->set_pll_cfg(dphy, cfg);
18762306a36Sopenharmony_ci}
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_cistatic unsigned long cdns_dphy_get_wakeup_time_ns(struct cdns_dphy *dphy)
19062306a36Sopenharmony_ci{
19162306a36Sopenharmony_ci	return dphy->ops->get_wakeup_time_ns(dphy);
19262306a36Sopenharmony_ci}
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_cistatic unsigned long cdns_dphy_ref_get_wakeup_time_ns(struct cdns_dphy *dphy)
19562306a36Sopenharmony_ci{
19662306a36Sopenharmony_ci	/* Default wakeup time is 800 ns (in a simulated environment). */
19762306a36Sopenharmony_ci	return 800;
19862306a36Sopenharmony_ci}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_cistatic void cdns_dphy_ref_set_pll_cfg(struct cdns_dphy *dphy,
20162306a36Sopenharmony_ci				      const struct cdns_dphy_cfg *cfg)
20262306a36Sopenharmony_ci{
20362306a36Sopenharmony_ci	u32 fbdiv_low, fbdiv_high;
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	fbdiv_low = (cfg->pll_fbdiv / 4) - 2;
20662306a36Sopenharmony_ci	fbdiv_high = cfg->pll_fbdiv - fbdiv_low - 2;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	writel(DPHY_CMN_IPDIV_FROM_REG | DPHY_CMN_OPDIV_FROM_REG |
20962306a36Sopenharmony_ci	       DPHY_CMN_IPDIV(cfg->pll_ipdiv) |
21062306a36Sopenharmony_ci	       DPHY_CMN_OPDIV(cfg->pll_opdiv),
21162306a36Sopenharmony_ci	       dphy->regs + DPHY_CMN_OPIPDIV);
21262306a36Sopenharmony_ci	writel(DPHY_CMN_FBDIV_FROM_REG |
21362306a36Sopenharmony_ci	       DPHY_CMN_FBDIV_VAL(fbdiv_low, fbdiv_high),
21462306a36Sopenharmony_ci	       dphy->regs + DPHY_CMN_FBDIV);
21562306a36Sopenharmony_ci	writel(DPHY_CMN_PWM_HIGH(6) | DPHY_CMN_PWM_LOW(0x101) |
21662306a36Sopenharmony_ci	       DPHY_CMN_PWM_DIV(0x8),
21762306a36Sopenharmony_ci	       dphy->regs + DPHY_CMN_PWM);
21862306a36Sopenharmony_ci}
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_cistatic void cdns_dphy_ref_set_psm_div(struct cdns_dphy *dphy, u8 div)
22162306a36Sopenharmony_ci{
22262306a36Sopenharmony_ci	writel(DPHY_PSM_CFG_FROM_REG | DPHY_PSM_CLK_DIV(div),
22362306a36Sopenharmony_ci	       dphy->regs + DPHY_PSM_CFG);
22462306a36Sopenharmony_ci}
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_cistatic unsigned long cdns_dphy_j721e_get_wakeup_time_ns(struct cdns_dphy *dphy)
22762306a36Sopenharmony_ci{
22862306a36Sopenharmony_ci	/* Minimum wakeup time as per MIPI D-PHY spec v1.2 */
22962306a36Sopenharmony_ci	return 1000000;
23062306a36Sopenharmony_ci}
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_cistatic void cdns_dphy_j721e_set_pll_cfg(struct cdns_dphy *dphy,
23362306a36Sopenharmony_ci					const struct cdns_dphy_cfg *cfg)
23462306a36Sopenharmony_ci{
23562306a36Sopenharmony_ci	u32 status;
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	/*
23862306a36Sopenharmony_ci	 * set the PWM and PLL Byteclk divider settings to recommended values
23962306a36Sopenharmony_ci	 * which is same as that of in ref ops
24062306a36Sopenharmony_ci	 */
24162306a36Sopenharmony_ci	writel(DPHY_CMN_PWM_HIGH(6) | DPHY_CMN_PWM_LOW(0x101) |
24262306a36Sopenharmony_ci	       DPHY_CMN_PWM_DIV(0x8),
24362306a36Sopenharmony_ci	       dphy->regs + DPHY_CMN_PWM);
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	writel((FIELD_PREP(DPHY_TX_J721E_WIZ_IPDIV, cfg->pll_ipdiv) |
24662306a36Sopenharmony_ci		FIELD_PREP(DPHY_TX_J721E_WIZ_OPDIV, cfg->pll_opdiv) |
24762306a36Sopenharmony_ci		FIELD_PREP(DPHY_TX_J721E_WIZ_FBDIV, cfg->pll_fbdiv)),
24862306a36Sopenharmony_ci		dphy->regs + DPHY_TX_J721E_WIZ_PLL_CTRL);
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	writel(DPHY_TX_J721E_WIZ_LANE_RSTB,
25162306a36Sopenharmony_ci	       dphy->regs + DPHY_TX_J721E_WIZ_RST_CTRL);
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	readl_poll_timeout(dphy->regs + DPHY_TX_J721E_WIZ_PLL_CTRL, status,
25462306a36Sopenharmony_ci			   (status & DPHY_TX_WIZ_PLL_LOCK), 0, POLL_TIMEOUT_US);
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	readl_poll_timeout(dphy->regs + DPHY_TX_J721E_WIZ_STATUS, status,
25762306a36Sopenharmony_ci			   (status & DPHY_TX_WIZ_O_CMN_READY), 0,
25862306a36Sopenharmony_ci			   POLL_TIMEOUT_US);
25962306a36Sopenharmony_ci}
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_cistatic void cdns_dphy_j721e_set_psm_div(struct cdns_dphy *dphy, u8 div)
26262306a36Sopenharmony_ci{
26362306a36Sopenharmony_ci	writel(div, dphy->regs + DPHY_TX_J721E_WIZ_PSM_FREQ);
26462306a36Sopenharmony_ci}
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci/*
26762306a36Sopenharmony_ci * This is the reference implementation of DPHY hooks. Specific integration of
26862306a36Sopenharmony_ci * this IP may have to re-implement some of them depending on how they decided
26962306a36Sopenharmony_ci * to wire things in the SoC.
27062306a36Sopenharmony_ci */
27162306a36Sopenharmony_cistatic const struct cdns_dphy_ops ref_dphy_ops = {
27262306a36Sopenharmony_ci	.get_wakeup_time_ns = cdns_dphy_ref_get_wakeup_time_ns,
27362306a36Sopenharmony_ci	.set_pll_cfg = cdns_dphy_ref_set_pll_cfg,
27462306a36Sopenharmony_ci	.set_psm_div = cdns_dphy_ref_set_psm_div,
27562306a36Sopenharmony_ci};
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_cistatic const struct cdns_dphy_ops j721e_dphy_ops = {
27862306a36Sopenharmony_ci	.get_wakeup_time_ns = cdns_dphy_j721e_get_wakeup_time_ns,
27962306a36Sopenharmony_ci	.set_pll_cfg = cdns_dphy_j721e_set_pll_cfg,
28062306a36Sopenharmony_ci	.set_psm_div = cdns_dphy_j721e_set_psm_div,
28162306a36Sopenharmony_ci};
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_cistatic int cdns_dphy_config_from_opts(struct phy *phy,
28462306a36Sopenharmony_ci				      struct phy_configure_opts_mipi_dphy *opts,
28562306a36Sopenharmony_ci				      struct cdns_dphy_cfg *cfg)
28662306a36Sopenharmony_ci{
28762306a36Sopenharmony_ci	struct cdns_dphy *dphy = phy_get_drvdata(phy);
28862306a36Sopenharmony_ci	unsigned int dsi_hfp_ext = 0;
28962306a36Sopenharmony_ci	int ret;
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci	ret = phy_mipi_dphy_config_validate(opts);
29262306a36Sopenharmony_ci	if (ret)
29362306a36Sopenharmony_ci		return ret;
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ci	ret = cdns_dsi_get_dphy_pll_cfg(dphy, cfg,
29662306a36Sopenharmony_ci					opts, &dsi_hfp_ext);
29762306a36Sopenharmony_ci	if (ret)
29862306a36Sopenharmony_ci		return ret;
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci	opts->wakeup = cdns_dphy_get_wakeup_time_ns(dphy) / 1000;
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci	return 0;
30362306a36Sopenharmony_ci}
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_cistatic int cdns_dphy_tx_get_band_ctrl(unsigned long hs_clk_rate)
30662306a36Sopenharmony_ci{
30762306a36Sopenharmony_ci	unsigned int rate;
30862306a36Sopenharmony_ci	int i;
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ci	rate = hs_clk_rate / 1000000UL;
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	if (rate < tx_bands[0])
31362306a36Sopenharmony_ci		return -EOPNOTSUPP;
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(tx_bands) - 1; i++) {
31662306a36Sopenharmony_ci		if (rate >= tx_bands[i] && rate < tx_bands[i + 1])
31762306a36Sopenharmony_ci			return i;
31862306a36Sopenharmony_ci	}
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ci	return -EOPNOTSUPP;
32162306a36Sopenharmony_ci}
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_cistatic int cdns_dphy_validate(struct phy *phy, enum phy_mode mode, int submode,
32462306a36Sopenharmony_ci			      union phy_configure_opts *opts)
32562306a36Sopenharmony_ci{
32662306a36Sopenharmony_ci	struct cdns_dphy_cfg cfg = { 0 };
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci	if (mode != PHY_MODE_MIPI_DPHY)
32962306a36Sopenharmony_ci		return -EINVAL;
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci	return cdns_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg);
33262306a36Sopenharmony_ci}
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_cistatic int cdns_dphy_configure(struct phy *phy, union phy_configure_opts *opts)
33562306a36Sopenharmony_ci{
33662306a36Sopenharmony_ci	struct cdns_dphy *dphy = phy_get_drvdata(phy);
33762306a36Sopenharmony_ci	struct cdns_dphy_cfg cfg = { 0 };
33862306a36Sopenharmony_ci	int ret, band_ctrl;
33962306a36Sopenharmony_ci	unsigned int reg;
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_ci	ret = cdns_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg);
34262306a36Sopenharmony_ci	if (ret)
34362306a36Sopenharmony_ci		return ret;
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ci	/*
34662306a36Sopenharmony_ci	 * Configure the internal PSM clk divider so that the DPHY has a
34762306a36Sopenharmony_ci	 * 1MHz clk (or something close).
34862306a36Sopenharmony_ci	 */
34962306a36Sopenharmony_ci	ret = cdns_dphy_setup_psm(dphy);
35062306a36Sopenharmony_ci	if (ret)
35162306a36Sopenharmony_ci		return ret;
35262306a36Sopenharmony_ci
35362306a36Sopenharmony_ci	/*
35462306a36Sopenharmony_ci	 * Configure attach clk lanes to data lanes: the DPHY has 2 clk lanes
35562306a36Sopenharmony_ci	 * and 8 data lanes, each clk lane can be attache different set of
35662306a36Sopenharmony_ci	 * data lanes. The 2 groups are named 'left' and 'right', so here we
35762306a36Sopenharmony_ci	 * just say that we want the 'left' clk lane to drive the 'left' data
35862306a36Sopenharmony_ci	 * lanes.
35962306a36Sopenharmony_ci	 */
36062306a36Sopenharmony_ci	cdns_dphy_set_clk_lane_cfg(dphy, DPHY_CLK_CFG_LEFT_DRIVES_LEFT);
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci	/*
36362306a36Sopenharmony_ci	 * Configure the DPHY PLL that will be used to generate the TX byte
36462306a36Sopenharmony_ci	 * clk.
36562306a36Sopenharmony_ci	 */
36662306a36Sopenharmony_ci	cdns_dphy_set_pll_cfg(dphy, &cfg);
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci	band_ctrl = cdns_dphy_tx_get_band_ctrl(opts->mipi_dphy.hs_clk_rate);
36962306a36Sopenharmony_ci	if (band_ctrl < 0)
37062306a36Sopenharmony_ci		return band_ctrl;
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_ci	reg = FIELD_PREP(DPHY_BAND_CFG_LEFT_BAND, band_ctrl) |
37362306a36Sopenharmony_ci	      FIELD_PREP(DPHY_BAND_CFG_RIGHT_BAND, band_ctrl);
37462306a36Sopenharmony_ci	writel(reg, dphy->regs + DPHY_BAND_CFG);
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_ci	return 0;
37762306a36Sopenharmony_ci}
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_cistatic int cdns_dphy_power_on(struct phy *phy)
38062306a36Sopenharmony_ci{
38162306a36Sopenharmony_ci	struct cdns_dphy *dphy = phy_get_drvdata(phy);
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ci	clk_prepare_enable(dphy->psm_clk);
38462306a36Sopenharmony_ci	clk_prepare_enable(dphy->pll_ref_clk);
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci	/* Start TX state machine. */
38762306a36Sopenharmony_ci	writel(DPHY_CMN_SSM_EN | DPHY_CMN_TX_MODE_EN,
38862306a36Sopenharmony_ci	       dphy->regs + DPHY_CMN_SSM);
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci	return 0;
39162306a36Sopenharmony_ci}
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_cistatic int cdns_dphy_power_off(struct phy *phy)
39462306a36Sopenharmony_ci{
39562306a36Sopenharmony_ci	struct cdns_dphy *dphy = phy_get_drvdata(phy);
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_ci	clk_disable_unprepare(dphy->pll_ref_clk);
39862306a36Sopenharmony_ci	clk_disable_unprepare(dphy->psm_clk);
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci	return 0;
40162306a36Sopenharmony_ci}
40262306a36Sopenharmony_ci
40362306a36Sopenharmony_cistatic const struct phy_ops cdns_dphy_ops = {
40462306a36Sopenharmony_ci	.configure	= cdns_dphy_configure,
40562306a36Sopenharmony_ci	.validate	= cdns_dphy_validate,
40662306a36Sopenharmony_ci	.power_on	= cdns_dphy_power_on,
40762306a36Sopenharmony_ci	.power_off	= cdns_dphy_power_off,
40862306a36Sopenharmony_ci};
40962306a36Sopenharmony_ci
41062306a36Sopenharmony_cistatic int cdns_dphy_probe(struct platform_device *pdev)
41162306a36Sopenharmony_ci{
41262306a36Sopenharmony_ci	struct phy_provider *phy_provider;
41362306a36Sopenharmony_ci	struct cdns_dphy *dphy;
41462306a36Sopenharmony_ci	int ret;
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_ci	dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL);
41762306a36Sopenharmony_ci	if (!dphy)
41862306a36Sopenharmony_ci		return -ENOMEM;
41962306a36Sopenharmony_ci	dev_set_drvdata(&pdev->dev, dphy);
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_ci	dphy->ops = of_device_get_match_data(&pdev->dev);
42262306a36Sopenharmony_ci	if (!dphy->ops)
42362306a36Sopenharmony_ci		return -EINVAL;
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci	dphy->regs = devm_platform_ioremap_resource(pdev, 0);
42662306a36Sopenharmony_ci	if (IS_ERR(dphy->regs))
42762306a36Sopenharmony_ci		return PTR_ERR(dphy->regs);
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_ci	dphy->psm_clk = devm_clk_get(&pdev->dev, "psm");
43062306a36Sopenharmony_ci	if (IS_ERR(dphy->psm_clk))
43162306a36Sopenharmony_ci		return PTR_ERR(dphy->psm_clk);
43262306a36Sopenharmony_ci
43362306a36Sopenharmony_ci	dphy->pll_ref_clk = devm_clk_get(&pdev->dev, "pll_ref");
43462306a36Sopenharmony_ci	if (IS_ERR(dphy->pll_ref_clk))
43562306a36Sopenharmony_ci		return PTR_ERR(dphy->pll_ref_clk);
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_ci	if (dphy->ops->probe) {
43862306a36Sopenharmony_ci		ret = dphy->ops->probe(dphy);
43962306a36Sopenharmony_ci		if (ret)
44062306a36Sopenharmony_ci			return ret;
44162306a36Sopenharmony_ci	}
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ci	dphy->phy = devm_phy_create(&pdev->dev, NULL, &cdns_dphy_ops);
44462306a36Sopenharmony_ci	if (IS_ERR(dphy->phy)) {
44562306a36Sopenharmony_ci		dev_err(&pdev->dev, "failed to create PHY\n");
44662306a36Sopenharmony_ci		if (dphy->ops->remove)
44762306a36Sopenharmony_ci			dphy->ops->remove(dphy);
44862306a36Sopenharmony_ci		return PTR_ERR(dphy->phy);
44962306a36Sopenharmony_ci	}
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_ci	phy_set_drvdata(dphy->phy, dphy);
45262306a36Sopenharmony_ci	phy_provider = devm_of_phy_provider_register(&pdev->dev,
45362306a36Sopenharmony_ci						     of_phy_simple_xlate);
45462306a36Sopenharmony_ci
45562306a36Sopenharmony_ci	return PTR_ERR_OR_ZERO(phy_provider);
45662306a36Sopenharmony_ci}
45762306a36Sopenharmony_ci
45862306a36Sopenharmony_cistatic void cdns_dphy_remove(struct platform_device *pdev)
45962306a36Sopenharmony_ci{
46062306a36Sopenharmony_ci	struct cdns_dphy *dphy = dev_get_drvdata(&pdev->dev);
46162306a36Sopenharmony_ci
46262306a36Sopenharmony_ci	if (dphy->ops->remove)
46362306a36Sopenharmony_ci		dphy->ops->remove(dphy);
46462306a36Sopenharmony_ci}
46562306a36Sopenharmony_ci
46662306a36Sopenharmony_cistatic const struct of_device_id cdns_dphy_of_match[] = {
46762306a36Sopenharmony_ci	{ .compatible = "cdns,dphy", .data = &ref_dphy_ops },
46862306a36Sopenharmony_ci	{ .compatible = "ti,j721e-dphy", .data = &j721e_dphy_ops },
46962306a36Sopenharmony_ci	{ /* sentinel */ },
47062306a36Sopenharmony_ci};
47162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, cdns_dphy_of_match);
47262306a36Sopenharmony_ci
47362306a36Sopenharmony_cistatic struct platform_driver cdns_dphy_platform_driver = {
47462306a36Sopenharmony_ci	.probe		= cdns_dphy_probe,
47562306a36Sopenharmony_ci	.remove_new	= cdns_dphy_remove,
47662306a36Sopenharmony_ci	.driver		= {
47762306a36Sopenharmony_ci		.name		= "cdns-mipi-dphy",
47862306a36Sopenharmony_ci		.of_match_table	= cdns_dphy_of_match,
47962306a36Sopenharmony_ci	},
48062306a36Sopenharmony_ci};
48162306a36Sopenharmony_cimodule_platform_driver(cdns_dphy_platform_driver);
48262306a36Sopenharmony_ci
48362306a36Sopenharmony_ciMODULE_AUTHOR("Maxime Ripard <maxime.ripard@bootlin.com>");
48462306a36Sopenharmony_ciMODULE_DESCRIPTION("Cadence MIPI D-PHY Driver");
48562306a36Sopenharmony_ciMODULE_LICENSE("GPL");
486