Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  1// SPDX-License-Identifier: GPL-2.0+
  2/*
  3 * Copyright: 2017-2018 Cadence Design Systems, Inc.
  4 */
  5
  6#include <linux/bitfield.h>
  7#include <linux/bitops.h>
  8#include <linux/clk.h>
  9#include <linux/io.h>
 10#include <linux/iopoll.h>
 11#include <linux/module.h>
 12#include <linux/of.h>
 13#include <linux/platform_device.h>
 14#include <linux/reset.h>
 15
 16#include <linux/phy/phy.h>
 17#include <linux/phy/phy-mipi-dphy.h>
 18
 19#define REG_WAKEUP_TIME_NS		800
 20#define DPHY_PLL_RATE_HZ		108000000
 21#define POLL_TIMEOUT_US			1000
 22
 23/* DPHY registers */
 24#define DPHY_PMA_CMN(reg)		(reg)
 25#define DPHY_PMA_LCLK(reg)		(0x100 + (reg))
 26#define DPHY_PMA_LDATA(lane, reg)	(0x200 + ((lane) * 0x100) + (reg))
 27#define DPHY_PMA_RCLK(reg)		(0x600 + (reg))
 28#define DPHY_PMA_RDATA(lane, reg)	(0x700 + ((lane) * 0x100) + (reg))
 29#define DPHY_PCS(reg)			(0xb00 + (reg))
 30
 31#define DPHY_CMN_SSM			DPHY_PMA_CMN(0x20)
 32#define DPHY_CMN_SSM_EN			BIT(0)
 33#define DPHY_CMN_TX_MODE_EN		BIT(9)
 34
 35#define DPHY_CMN_PWM			DPHY_PMA_CMN(0x40)
 36#define DPHY_CMN_PWM_DIV(x)		((x) << 20)
 37#define DPHY_CMN_PWM_LOW(x)		((x) << 10)
 38#define DPHY_CMN_PWM_HIGH(x)		(x)
 39
 40#define DPHY_CMN_FBDIV			DPHY_PMA_CMN(0x4c)
 41#define DPHY_CMN_FBDIV_VAL(low, high)	(((high) << 11) | ((low) << 22))
 42#define DPHY_CMN_FBDIV_FROM_REG		(BIT(10) | BIT(21))
 43
 44#define DPHY_CMN_OPIPDIV		DPHY_PMA_CMN(0x50)
 45#define DPHY_CMN_IPDIV_FROM_REG		BIT(0)
 46#define DPHY_CMN_IPDIV(x)		((x) << 1)
 47#define DPHY_CMN_OPDIV_FROM_REG		BIT(6)
 48#define DPHY_CMN_OPDIV(x)		((x) << 7)
 49
 50#define DPHY_BAND_CFG			DPHY_PCS(0x0)
 51#define DPHY_BAND_CFG_LEFT_BAND		GENMASK(4, 0)
 52#define DPHY_BAND_CFG_RIGHT_BAND	GENMASK(9, 5)
 53
 54#define DPHY_PSM_CFG			DPHY_PCS(0x4)
 55#define DPHY_PSM_CFG_FROM_REG		BIT(0)
 56#define DPHY_PSM_CLK_DIV(x)		((x) << 1)
 57
 58#define DSI_HBP_FRAME_OVERHEAD		12
 59#define DSI_HSA_FRAME_OVERHEAD		14
 60#define DSI_HFP_FRAME_OVERHEAD		6
 61#define DSI_HSS_VSS_VSE_FRAME_OVERHEAD	4
 62#define DSI_BLANKING_FRAME_OVERHEAD	6
 63#define DSI_NULL_FRAME_OVERHEAD		6
 64#define DSI_EOT_PKT_SIZE		4
 65
 66#define DPHY_TX_J721E_WIZ_PLL_CTRL	0xF04
 67#define DPHY_TX_J721E_WIZ_STATUS	0xF08
 68#define DPHY_TX_J721E_WIZ_RST_CTRL	0xF0C
 69#define DPHY_TX_J721E_WIZ_PSM_FREQ	0xF10
 70
 71#define DPHY_TX_J721E_WIZ_IPDIV		GENMASK(4, 0)
 72#define DPHY_TX_J721E_WIZ_OPDIV		GENMASK(13, 8)
 73#define DPHY_TX_J721E_WIZ_FBDIV		GENMASK(25, 16)
 74#define DPHY_TX_J721E_WIZ_LANE_RSTB	BIT(31)
 75#define DPHY_TX_WIZ_PLL_LOCK		BIT(31)
 76#define DPHY_TX_WIZ_O_CMN_READY		BIT(31)
 77
 78struct cdns_dphy_cfg {
 79	u8 pll_ipdiv;
 80	u8 pll_opdiv;
 81	u16 pll_fbdiv;
 82	unsigned int nlanes;
 83};
 84
 85enum cdns_dphy_clk_lane_cfg {
 86	DPHY_CLK_CFG_LEFT_DRIVES_ALL = 0,
 87	DPHY_CLK_CFG_LEFT_DRIVES_RIGHT = 1,
 88	DPHY_CLK_CFG_LEFT_DRIVES_LEFT = 2,
 89	DPHY_CLK_CFG_RIGHT_DRIVES_ALL = 3,
 90};
 91
 92struct cdns_dphy;
 93struct cdns_dphy_ops {
 94	int (*probe)(struct cdns_dphy *dphy);
 95	void (*remove)(struct cdns_dphy *dphy);
 96	void (*set_psm_div)(struct cdns_dphy *dphy, u8 div);
 97	void (*set_clk_lane_cfg)(struct cdns_dphy *dphy,
 98				 enum cdns_dphy_clk_lane_cfg cfg);
 99	void (*set_pll_cfg)(struct cdns_dphy *dphy,
100			    const struct cdns_dphy_cfg *cfg);
101	unsigned long (*get_wakeup_time_ns)(struct cdns_dphy *dphy);
102};
103
104struct cdns_dphy {
105	struct cdns_dphy_cfg cfg;
106	void __iomem *regs;
107	struct clk *psm_clk;
108	struct clk *pll_ref_clk;
109	const struct cdns_dphy_ops *ops;
110	struct phy *phy;
111};
112
113/* Order of bands is important since the index is the band number. */
114static const unsigned int tx_bands[] = {
115	80, 100, 120, 160, 200, 240, 320, 390, 450, 510, 560, 640, 690, 770,
116	870, 950, 1000, 1200, 1400, 1600, 1800, 2000, 2200, 2500
117};
118
119static int cdns_dsi_get_dphy_pll_cfg(struct cdns_dphy *dphy,
120				     struct cdns_dphy_cfg *cfg,
121				     struct phy_configure_opts_mipi_dphy *opts,
122				     unsigned int *dsi_hfp_ext)
123{
124	unsigned long pll_ref_hz = clk_get_rate(dphy->pll_ref_clk);
125	u64 dlane_bps;
126
127	memset(cfg, 0, sizeof(*cfg));
128
129	if (pll_ref_hz < 9600000 || pll_ref_hz >= 150000000)
130		return -EINVAL;
131	else if (pll_ref_hz < 19200000)
132		cfg->pll_ipdiv = 1;
133	else if (pll_ref_hz < 38400000)
134		cfg->pll_ipdiv = 2;
135	else if (pll_ref_hz < 76800000)
136		cfg->pll_ipdiv = 4;
137	else
138		cfg->pll_ipdiv = 8;
139
140	dlane_bps = opts->hs_clk_rate;
141
142	if (dlane_bps > 2500000000UL || dlane_bps < 160000000UL)
143		return -EINVAL;
144	else if (dlane_bps >= 1250000000)
145		cfg->pll_opdiv = 1;
146	else if (dlane_bps >= 630000000)
147		cfg->pll_opdiv = 2;
148	else if (dlane_bps >= 320000000)
149		cfg->pll_opdiv = 4;
150	else if (dlane_bps >= 160000000)
151		cfg->pll_opdiv = 8;
152
153	cfg->pll_fbdiv = DIV_ROUND_UP_ULL(dlane_bps * 2 * cfg->pll_opdiv *
154					  cfg->pll_ipdiv,
155					  pll_ref_hz);
156
157	return 0;
158}
159
160static int cdns_dphy_setup_psm(struct cdns_dphy *dphy)
161{
162	unsigned long psm_clk_hz = clk_get_rate(dphy->psm_clk);
163	unsigned long psm_div;
164
165	if (!psm_clk_hz || psm_clk_hz > 100000000)
166		return -EINVAL;
167
168	psm_div = DIV_ROUND_CLOSEST(psm_clk_hz, 1000000);
169	if (dphy->ops->set_psm_div)
170		dphy->ops->set_psm_div(dphy, psm_div);
171
172	return 0;
173}
174
175static void cdns_dphy_set_clk_lane_cfg(struct cdns_dphy *dphy,
176				       enum cdns_dphy_clk_lane_cfg cfg)
177{
178	if (dphy->ops->set_clk_lane_cfg)
179		dphy->ops->set_clk_lane_cfg(dphy, cfg);
180}
181
182static void cdns_dphy_set_pll_cfg(struct cdns_dphy *dphy,
183				  const struct cdns_dphy_cfg *cfg)
184{
185	if (dphy->ops->set_pll_cfg)
186		dphy->ops->set_pll_cfg(dphy, cfg);
187}
188
189static unsigned long cdns_dphy_get_wakeup_time_ns(struct cdns_dphy *dphy)
190{
191	return dphy->ops->get_wakeup_time_ns(dphy);
192}
193
194static unsigned long cdns_dphy_ref_get_wakeup_time_ns(struct cdns_dphy *dphy)
195{
196	/* Default wakeup time is 800 ns (in a simulated environment). */
197	return 800;
198}
199
200static void cdns_dphy_ref_set_pll_cfg(struct cdns_dphy *dphy,
201				      const struct cdns_dphy_cfg *cfg)
202{
203	u32 fbdiv_low, fbdiv_high;
204
205	fbdiv_low = (cfg->pll_fbdiv / 4) - 2;
206	fbdiv_high = cfg->pll_fbdiv - fbdiv_low - 2;
207
208	writel(DPHY_CMN_IPDIV_FROM_REG | DPHY_CMN_OPDIV_FROM_REG |
209	       DPHY_CMN_IPDIV(cfg->pll_ipdiv) |
210	       DPHY_CMN_OPDIV(cfg->pll_opdiv),
211	       dphy->regs + DPHY_CMN_OPIPDIV);
212	writel(DPHY_CMN_FBDIV_FROM_REG |
213	       DPHY_CMN_FBDIV_VAL(fbdiv_low, fbdiv_high),
214	       dphy->regs + DPHY_CMN_FBDIV);
215	writel(DPHY_CMN_PWM_HIGH(6) | DPHY_CMN_PWM_LOW(0x101) |
216	       DPHY_CMN_PWM_DIV(0x8),
217	       dphy->regs + DPHY_CMN_PWM);
218}
219
220static void cdns_dphy_ref_set_psm_div(struct cdns_dphy *dphy, u8 div)
221{
222	writel(DPHY_PSM_CFG_FROM_REG | DPHY_PSM_CLK_DIV(div),
223	       dphy->regs + DPHY_PSM_CFG);
224}
225
226static unsigned long cdns_dphy_j721e_get_wakeup_time_ns(struct cdns_dphy *dphy)
227{
228	/* Minimum wakeup time as per MIPI D-PHY spec v1.2 */
229	return 1000000;
230}
231
232static void cdns_dphy_j721e_set_pll_cfg(struct cdns_dphy *dphy,
233					const struct cdns_dphy_cfg *cfg)
234{
235	u32 status;
236
237	/*
238	 * set the PWM and PLL Byteclk divider settings to recommended values
239	 * which is same as that of in ref ops
240	 */
241	writel(DPHY_CMN_PWM_HIGH(6) | DPHY_CMN_PWM_LOW(0x101) |
242	       DPHY_CMN_PWM_DIV(0x8),
243	       dphy->regs + DPHY_CMN_PWM);
244
245	writel((FIELD_PREP(DPHY_TX_J721E_WIZ_IPDIV, cfg->pll_ipdiv) |
246		FIELD_PREP(DPHY_TX_J721E_WIZ_OPDIV, cfg->pll_opdiv) |
247		FIELD_PREP(DPHY_TX_J721E_WIZ_FBDIV, cfg->pll_fbdiv)),
248		dphy->regs + DPHY_TX_J721E_WIZ_PLL_CTRL);
249
250	writel(DPHY_TX_J721E_WIZ_LANE_RSTB,
251	       dphy->regs + DPHY_TX_J721E_WIZ_RST_CTRL);
252
253	readl_poll_timeout(dphy->regs + DPHY_TX_J721E_WIZ_PLL_CTRL, status,
254			   (status & DPHY_TX_WIZ_PLL_LOCK), 0, POLL_TIMEOUT_US);
255
256	readl_poll_timeout(dphy->regs + DPHY_TX_J721E_WIZ_STATUS, status,
257			   (status & DPHY_TX_WIZ_O_CMN_READY), 0,
258			   POLL_TIMEOUT_US);
259}
260
261static void cdns_dphy_j721e_set_psm_div(struct cdns_dphy *dphy, u8 div)
262{
263	writel(div, dphy->regs + DPHY_TX_J721E_WIZ_PSM_FREQ);
264}
265
266/*
267 * This is the reference implementation of DPHY hooks. Specific integration of
268 * this IP may have to re-implement some of them depending on how they decided
269 * to wire things in the SoC.
270 */
271static const struct cdns_dphy_ops ref_dphy_ops = {
272	.get_wakeup_time_ns = cdns_dphy_ref_get_wakeup_time_ns,
273	.set_pll_cfg = cdns_dphy_ref_set_pll_cfg,
274	.set_psm_div = cdns_dphy_ref_set_psm_div,
275};
276
277static const struct cdns_dphy_ops j721e_dphy_ops = {
278	.get_wakeup_time_ns = cdns_dphy_j721e_get_wakeup_time_ns,
279	.set_pll_cfg = cdns_dphy_j721e_set_pll_cfg,
280	.set_psm_div = cdns_dphy_j721e_set_psm_div,
281};
282
283static int cdns_dphy_config_from_opts(struct phy *phy,
284				      struct phy_configure_opts_mipi_dphy *opts,
285				      struct cdns_dphy_cfg *cfg)
286{
287	struct cdns_dphy *dphy = phy_get_drvdata(phy);
288	unsigned int dsi_hfp_ext = 0;
289	int ret;
290
291	ret = phy_mipi_dphy_config_validate(opts);
292	if (ret)
293		return ret;
294
295	ret = cdns_dsi_get_dphy_pll_cfg(dphy, cfg,
296					opts, &dsi_hfp_ext);
297	if (ret)
298		return ret;
299
300	opts->wakeup = cdns_dphy_get_wakeup_time_ns(dphy) / 1000;
301
302	return 0;
303}
304
305static int cdns_dphy_tx_get_band_ctrl(unsigned long hs_clk_rate)
306{
307	unsigned int rate;
308	int i;
309
310	rate = hs_clk_rate / 1000000UL;
311
312	if (rate < tx_bands[0])
313		return -EOPNOTSUPP;
314
315	for (i = 0; i < ARRAY_SIZE(tx_bands) - 1; i++) {
316		if (rate >= tx_bands[i] && rate < tx_bands[i + 1])
317			return i;
318	}
319
320	return -EOPNOTSUPP;
321}
322
323static int cdns_dphy_validate(struct phy *phy, enum phy_mode mode, int submode,
324			      union phy_configure_opts *opts)
325{
326	struct cdns_dphy_cfg cfg = { 0 };
327
328	if (mode != PHY_MODE_MIPI_DPHY)
329		return -EINVAL;
330
331	return cdns_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg);
332}
333
334static int cdns_dphy_configure(struct phy *phy, union phy_configure_opts *opts)
335{
336	struct cdns_dphy *dphy = phy_get_drvdata(phy);
337	struct cdns_dphy_cfg cfg = { 0 };
338	int ret, band_ctrl;
339	unsigned int reg;
340
341	ret = cdns_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg);
342	if (ret)
343		return ret;
344
345	/*
346	 * Configure the internal PSM clk divider so that the DPHY has a
347	 * 1MHz clk (or something close).
348	 */
349	ret = cdns_dphy_setup_psm(dphy);
350	if (ret)
351		return ret;
352
353	/*
354	 * Configure attach clk lanes to data lanes: the DPHY has 2 clk lanes
355	 * and 8 data lanes, each clk lane can be attache different set of
356	 * data lanes. The 2 groups are named 'left' and 'right', so here we
357	 * just say that we want the 'left' clk lane to drive the 'left' data
358	 * lanes.
359	 */
360	cdns_dphy_set_clk_lane_cfg(dphy, DPHY_CLK_CFG_LEFT_DRIVES_LEFT);
361
362	/*
363	 * Configure the DPHY PLL that will be used to generate the TX byte
364	 * clk.
365	 */
366	cdns_dphy_set_pll_cfg(dphy, &cfg);
367
368	band_ctrl = cdns_dphy_tx_get_band_ctrl(opts->mipi_dphy.hs_clk_rate);
369	if (band_ctrl < 0)
370		return band_ctrl;
371
372	reg = FIELD_PREP(DPHY_BAND_CFG_LEFT_BAND, band_ctrl) |
373	      FIELD_PREP(DPHY_BAND_CFG_RIGHT_BAND, band_ctrl);
374	writel(reg, dphy->regs + DPHY_BAND_CFG);
375
376	return 0;
377}
378
379static int cdns_dphy_power_on(struct phy *phy)
380{
381	struct cdns_dphy *dphy = phy_get_drvdata(phy);
382
383	clk_prepare_enable(dphy->psm_clk);
384	clk_prepare_enable(dphy->pll_ref_clk);
385
386	/* Start TX state machine. */
387	writel(DPHY_CMN_SSM_EN | DPHY_CMN_TX_MODE_EN,
388	       dphy->regs + DPHY_CMN_SSM);
389
390	return 0;
391}
392
393static int cdns_dphy_power_off(struct phy *phy)
394{
395	struct cdns_dphy *dphy = phy_get_drvdata(phy);
396
397	clk_disable_unprepare(dphy->pll_ref_clk);
398	clk_disable_unprepare(dphy->psm_clk);
399
400	return 0;
401}
402
403static const struct phy_ops cdns_dphy_ops = {
404	.configure	= cdns_dphy_configure,
405	.validate	= cdns_dphy_validate,
406	.power_on	= cdns_dphy_power_on,
407	.power_off	= cdns_dphy_power_off,
408};
409
410static int cdns_dphy_probe(struct platform_device *pdev)
411{
412	struct phy_provider *phy_provider;
413	struct cdns_dphy *dphy;
414	int ret;
415
416	dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL);
417	if (!dphy)
418		return -ENOMEM;
419	dev_set_drvdata(&pdev->dev, dphy);
420
421	dphy->ops = of_device_get_match_data(&pdev->dev);
422	if (!dphy->ops)
423		return -EINVAL;
424
425	dphy->regs = devm_platform_ioremap_resource(pdev, 0);
426	if (IS_ERR(dphy->regs))
427		return PTR_ERR(dphy->regs);
428
429	dphy->psm_clk = devm_clk_get(&pdev->dev, "psm");
430	if (IS_ERR(dphy->psm_clk))
431		return PTR_ERR(dphy->psm_clk);
432
433	dphy->pll_ref_clk = devm_clk_get(&pdev->dev, "pll_ref");
434	if (IS_ERR(dphy->pll_ref_clk))
435		return PTR_ERR(dphy->pll_ref_clk);
436
437	if (dphy->ops->probe) {
438		ret = dphy->ops->probe(dphy);
439		if (ret)
440			return ret;
441	}
442
443	dphy->phy = devm_phy_create(&pdev->dev, NULL, &cdns_dphy_ops);
444	if (IS_ERR(dphy->phy)) {
445		dev_err(&pdev->dev, "failed to create PHY\n");
446		if (dphy->ops->remove)
447			dphy->ops->remove(dphy);
448		return PTR_ERR(dphy->phy);
449	}
450
451	phy_set_drvdata(dphy->phy, dphy);
452	phy_provider = devm_of_phy_provider_register(&pdev->dev,
453						     of_phy_simple_xlate);
454
455	return PTR_ERR_OR_ZERO(phy_provider);
456}
457
458static void cdns_dphy_remove(struct platform_device *pdev)
459{
460	struct cdns_dphy *dphy = dev_get_drvdata(&pdev->dev);
461
462	if (dphy->ops->remove)
463		dphy->ops->remove(dphy);
464}
465
466static const struct of_device_id cdns_dphy_of_match[] = {
467	{ .compatible = "cdns,dphy", .data = &ref_dphy_ops },
468	{ .compatible = "ti,j721e-dphy", .data = &j721e_dphy_ops },
469	{ /* sentinel */ },
470};
471MODULE_DEVICE_TABLE(of, cdns_dphy_of_match);
472
473static struct platform_driver cdns_dphy_platform_driver = {
474	.probe		= cdns_dphy_probe,
475	.remove_new	= cdns_dphy_remove,
476	.driver		= {
477		.name		= "cdns-mipi-dphy",
478		.of_match_table	= cdns_dphy_of_match,
479	},
480};
481module_platform_driver(cdns_dphy_platform_driver);
482
483MODULE_AUTHOR("Maxime Ripard <maxime.ripard@bootlin.com>");
484MODULE_DESCRIPTION("Cadence MIPI D-PHY Driver");
485MODULE_LICENSE("GPL");