141d393aaSPratyush Yadav // SPDX-License-Identifier: GPL-2.0-only
241d393aaSPratyush Yadav /*
341d393aaSPratyush Yadav * Copyright (C) 2022 Texas Instruments Incorporated - https://www.ti.com/
441d393aaSPratyush Yadav */
541d393aaSPratyush Yadav
641d393aaSPratyush Yadav #include <linux/bitfield.h>
741d393aaSPratyush Yadav #include <linux/bitops.h>
841d393aaSPratyush Yadav #include <linux/io.h>
941d393aaSPratyush Yadav #include <linux/iopoll.h>
10*7559e757SRob Herring #include <linux/mod_devicetable.h>
1141d393aaSPratyush Yadav #include <linux/module.h>
1241d393aaSPratyush Yadav #include <linux/phy/phy.h>
1341d393aaSPratyush Yadav #include <linux/phy/phy-mipi-dphy.h>
1441d393aaSPratyush Yadav #include <linux/platform_device.h>
15a0106132SSinthu Raja #include <linux/sys_soc.h>
1641d393aaSPratyush Yadav
1741d393aaSPratyush Yadav #define DPHY_PMA_CMN(reg) (reg)
1841d393aaSPratyush Yadav #define DPHY_PCS(reg) (0xb00 + (reg))
1941d393aaSPratyush Yadav #define DPHY_ISO(reg) (0xc00 + (reg))
20a0106132SSinthu Raja #define DPHY_WRAP(reg) (0x1000 + (reg))
2141d393aaSPratyush Yadav
2241d393aaSPratyush Yadav #define DPHY_CMN_SSM DPHY_PMA_CMN(0x20)
2341d393aaSPratyush Yadav #define DPHY_CMN_RX_MODE_EN BIT(10)
2441d393aaSPratyush Yadav #define DPHY_CMN_RX_BANDGAP_TIMER_MASK GENMASK(8, 1)
2541d393aaSPratyush Yadav #define DPHY_CMN_SSM_EN BIT(0)
2641d393aaSPratyush Yadav
2741d393aaSPratyush Yadav #define DPHY_CMN_RX_BANDGAP_TIMER 0x14
2841d393aaSPratyush Yadav
2941d393aaSPratyush Yadav #define DPHY_BAND_CFG DPHY_PCS(0x0)
3041d393aaSPratyush Yadav #define DPHY_BAND_CFG_RIGHT_BAND GENMASK(9, 5)
3141d393aaSPratyush Yadav #define DPHY_BAND_CFG_LEFT_BAND GENMASK(4, 0)
3241d393aaSPratyush Yadav
3341d393aaSPratyush Yadav #define DPHY_POWER_ISLAND_EN_DATA DPHY_PCS(0x8)
3441d393aaSPratyush Yadav #define DPHY_POWER_ISLAND_EN_DATA_VAL 0xaaaaaaaa
3541d393aaSPratyush Yadav
3641d393aaSPratyush Yadav #define DPHY_POWER_ISLAND_EN_CLK DPHY_PCS(0xc)
3741d393aaSPratyush Yadav #define DPHY_POWER_ISLAND_EN_CLK_VAL 0xaa
3841d393aaSPratyush Yadav
39a0106132SSinthu Raja #define DPHY_LANE DPHY_WRAP(0x0)
40a0106132SSinthu Raja #define DPHY_LANE_RESET_CMN_EN BIT(23)
41a0106132SSinthu Raja
4241d393aaSPratyush Yadav #define DPHY_ISO_CL_CTRL_L DPHY_ISO(0x10)
4341d393aaSPratyush Yadav #define DPHY_ISO_DL_CTRL_L0 DPHY_ISO(0x14)
4441d393aaSPratyush Yadav #define DPHY_ISO_DL_CTRL_L1 DPHY_ISO(0x20)
4541d393aaSPratyush Yadav #define DPHY_ISO_DL_CTRL_L2 DPHY_ISO(0x30)
4641d393aaSPratyush Yadav #define DPHY_ISO_DL_CTRL_L3 DPHY_ISO(0x3c)
4741d393aaSPratyush Yadav
4841d393aaSPratyush Yadav #define DPHY_ISO_LANE_READY_BIT 0
4941d393aaSPratyush Yadav #define DPHY_ISO_LANE_READY_TIMEOUT_MS 100UL
5041d393aaSPratyush Yadav
5141d393aaSPratyush Yadav #define DPHY_LANES_MIN 1
5241d393aaSPratyush Yadav #define DPHY_LANES_MAX 4
5341d393aaSPratyush Yadav
5441d393aaSPratyush Yadav struct cdns_dphy_rx {
5541d393aaSPratyush Yadav void __iomem *regs;
5641d393aaSPratyush Yadav struct device *dev;
5741d393aaSPratyush Yadav struct phy *phy;
5841d393aaSPratyush Yadav };
5941d393aaSPratyush Yadav
6041d393aaSPratyush Yadav struct cdns_dphy_rx_band {
6141d393aaSPratyush Yadav /* Rates are in Mbps. */
6241d393aaSPratyush Yadav unsigned int min_rate;
6341d393aaSPratyush Yadav unsigned int max_rate;
6441d393aaSPratyush Yadav };
6541d393aaSPratyush Yadav
66a0106132SSinthu Raja struct cdns_dphy_soc_data {
67a0106132SSinthu Raja bool has_hw_cmn_rstb;
68a0106132SSinthu Raja };
69a0106132SSinthu Raja
7041d393aaSPratyush Yadav /* Order of bands is important since the index is the band number. */
7141d393aaSPratyush Yadav static const struct cdns_dphy_rx_band bands[] = {
7241d393aaSPratyush Yadav { 80, 100 }, { 100, 120 }, { 120, 160 }, { 160, 200 }, { 200, 240 },
7341d393aaSPratyush Yadav { 240, 280 }, { 280, 320 }, { 320, 360 }, { 360, 400 }, { 400, 480 },
7441d393aaSPratyush Yadav { 480, 560 }, { 560, 640 }, { 640, 720 }, { 720, 800 }, { 800, 880 },
7541d393aaSPratyush Yadav { 880, 1040 }, { 1040, 1200 }, { 1200, 1350 }, { 1350, 1500 },
7641d393aaSPratyush Yadav { 1500, 1750 }, { 1750, 2000 }, { 2000, 2250 }, { 2250, 2500 }
7741d393aaSPratyush Yadav };
7841d393aaSPratyush Yadav
cdns_dphy_rx_power_on(struct phy * phy)7941d393aaSPratyush Yadav static int cdns_dphy_rx_power_on(struct phy *phy)
8041d393aaSPratyush Yadav {
8141d393aaSPratyush Yadav struct cdns_dphy_rx *dphy = phy_get_drvdata(phy);
8241d393aaSPratyush Yadav
8341d393aaSPratyush Yadav /* Start RX state machine. */
8441d393aaSPratyush Yadav writel(DPHY_CMN_SSM_EN | DPHY_CMN_RX_MODE_EN |
8541d393aaSPratyush Yadav FIELD_PREP(DPHY_CMN_RX_BANDGAP_TIMER_MASK,
8641d393aaSPratyush Yadav DPHY_CMN_RX_BANDGAP_TIMER),
8741d393aaSPratyush Yadav dphy->regs + DPHY_CMN_SSM);
8841d393aaSPratyush Yadav
8941d393aaSPratyush Yadav return 0;
9041d393aaSPratyush Yadav }
9141d393aaSPratyush Yadav
cdns_dphy_rx_power_off(struct phy * phy)9241d393aaSPratyush Yadav static int cdns_dphy_rx_power_off(struct phy *phy)
9341d393aaSPratyush Yadav {
9441d393aaSPratyush Yadav struct cdns_dphy_rx *dphy = phy_get_drvdata(phy);
9541d393aaSPratyush Yadav
9641d393aaSPratyush Yadav writel(0, dphy->regs + DPHY_CMN_SSM);
9741d393aaSPratyush Yadav
9841d393aaSPratyush Yadav return 0;
9941d393aaSPratyush Yadav }
10041d393aaSPratyush Yadav
cdns_dphy_rx_get_band_ctrl(unsigned long hs_clk_rate)10141d393aaSPratyush Yadav static int cdns_dphy_rx_get_band_ctrl(unsigned long hs_clk_rate)
10241d393aaSPratyush Yadav {
10341d393aaSPratyush Yadav unsigned int rate, i;
10441d393aaSPratyush Yadav
10541d393aaSPratyush Yadav rate = hs_clk_rate / 1000000UL;
10641d393aaSPratyush Yadav /* Since CSI-2 clock is DDR, the bit rate is twice the clock rate. */
10741d393aaSPratyush Yadav rate *= 2;
10841d393aaSPratyush Yadav
10941d393aaSPratyush Yadav if (rate < bands[0].min_rate)
11041d393aaSPratyush Yadav return -EOPNOTSUPP;
11141d393aaSPratyush Yadav
11241d393aaSPratyush Yadav for (i = 0; i < ARRAY_SIZE(bands); i++)
11341d393aaSPratyush Yadav if (rate < bands[i].max_rate)
11441d393aaSPratyush Yadav return i;
11541d393aaSPratyush Yadav
11641d393aaSPratyush Yadav return -EOPNOTSUPP;
11741d393aaSPratyush Yadav }
11841d393aaSPratyush Yadav
cdns_dphy_rx_wait_for_bit(void __iomem * addr,unsigned int bit)11941d393aaSPratyush Yadav static inline int cdns_dphy_rx_wait_for_bit(void __iomem *addr,
12041d393aaSPratyush Yadav unsigned int bit)
12141d393aaSPratyush Yadav {
12241d393aaSPratyush Yadav u32 val;
12341d393aaSPratyush Yadav
12441d393aaSPratyush Yadav return readl_relaxed_poll_timeout(addr, val, val & BIT(bit), 10,
12541d393aaSPratyush Yadav DPHY_ISO_LANE_READY_TIMEOUT_MS * 1000);
12641d393aaSPratyush Yadav }
12741d393aaSPratyush Yadav
cdns_dphy_rx_wait_lane_ready(struct cdns_dphy_rx * dphy,unsigned int lanes)12841d393aaSPratyush Yadav static int cdns_dphy_rx_wait_lane_ready(struct cdns_dphy_rx *dphy,
12941d393aaSPratyush Yadav unsigned int lanes)
13041d393aaSPratyush Yadav {
13141d393aaSPratyush Yadav static const u32 data_lane_ctrl[] = {DPHY_ISO_DL_CTRL_L0,
13241d393aaSPratyush Yadav DPHY_ISO_DL_CTRL_L1,
13341d393aaSPratyush Yadav DPHY_ISO_DL_CTRL_L2,
13441d393aaSPratyush Yadav DPHY_ISO_DL_CTRL_L3};
13541d393aaSPratyush Yadav void __iomem *reg = dphy->regs;
13641d393aaSPratyush Yadav unsigned int i;
13741d393aaSPratyush Yadav int ret;
13841d393aaSPratyush Yadav
13941d393aaSPratyush Yadav /* Clock lane */
14041d393aaSPratyush Yadav ret = cdns_dphy_rx_wait_for_bit(reg + DPHY_ISO_CL_CTRL_L,
14141d393aaSPratyush Yadav DPHY_ISO_LANE_READY_BIT);
14241d393aaSPratyush Yadav if (ret)
14341d393aaSPratyush Yadav return ret;
14441d393aaSPratyush Yadav
14541d393aaSPratyush Yadav for (i = 0; i < lanes; i++) {
14641d393aaSPratyush Yadav ret = cdns_dphy_rx_wait_for_bit(reg + data_lane_ctrl[i],
14741d393aaSPratyush Yadav DPHY_ISO_LANE_READY_BIT);
14841d393aaSPratyush Yadav if (ret)
14941d393aaSPratyush Yadav return ret;
15041d393aaSPratyush Yadav }
15141d393aaSPratyush Yadav
15241d393aaSPratyush Yadav return 0;
15341d393aaSPratyush Yadav }
15441d393aaSPratyush Yadav
155a0106132SSinthu Raja static struct cdns_dphy_soc_data j721e_soc_data = {
156a0106132SSinthu Raja .has_hw_cmn_rstb = true,
157a0106132SSinthu Raja };
158a0106132SSinthu Raja
159a0106132SSinthu Raja static const struct soc_device_attribute cdns_dphy_socinfo[] = {
160a0106132SSinthu Raja {
161a0106132SSinthu Raja .family = "J721E",
162a0106132SSinthu Raja .revision = "SR1.0",
163a0106132SSinthu Raja .data = &j721e_soc_data,
164a0106132SSinthu Raja },
165a0106132SSinthu Raja {/* sentinel */}
166a0106132SSinthu Raja };
167a0106132SSinthu Raja
cdns_dphy_rx_configure(struct phy * phy,union phy_configure_opts * opts)16841d393aaSPratyush Yadav static int cdns_dphy_rx_configure(struct phy *phy,
16941d393aaSPratyush Yadav union phy_configure_opts *opts)
17041d393aaSPratyush Yadav {
17141d393aaSPratyush Yadav struct cdns_dphy_rx *dphy = phy_get_drvdata(phy);
17241d393aaSPratyush Yadav unsigned int reg, lanes = opts->mipi_dphy.lanes;
173a0106132SSinthu Raja const struct cdns_dphy_soc_data *soc_data = NULL;
174a0106132SSinthu Raja const struct soc_device_attribute *soc;
17541d393aaSPratyush Yadav int band_ctrl, ret;
17641d393aaSPratyush Yadav
177a0106132SSinthu Raja soc = soc_device_match(cdns_dphy_socinfo);
178a0106132SSinthu Raja if (soc && soc->data)
179a0106132SSinthu Raja soc_data = soc->data;
180a0106132SSinthu Raja if (!soc || (soc_data && !soc_data->has_hw_cmn_rstb)) {
181a0106132SSinthu Raja reg = DPHY_LANE_RESET_CMN_EN;
182a0106132SSinthu Raja writel(reg, dphy->regs + DPHY_LANE);
183a0106132SSinthu Raja }
184a0106132SSinthu Raja
18541d393aaSPratyush Yadav /* Data lanes. Minimum one lane is mandatory. */
18641d393aaSPratyush Yadav if (lanes < DPHY_LANES_MIN || lanes > DPHY_LANES_MAX)
18741d393aaSPratyush Yadav return -EINVAL;
18841d393aaSPratyush Yadav
18941d393aaSPratyush Yadav band_ctrl = cdns_dphy_rx_get_band_ctrl(opts->mipi_dphy.hs_clk_rate);
19041d393aaSPratyush Yadav if (band_ctrl < 0)
19141d393aaSPratyush Yadav return band_ctrl;
19241d393aaSPratyush Yadav
19341d393aaSPratyush Yadav reg = FIELD_PREP(DPHY_BAND_CFG_LEFT_BAND, band_ctrl) |
19441d393aaSPratyush Yadav FIELD_PREP(DPHY_BAND_CFG_RIGHT_BAND, band_ctrl);
19541d393aaSPratyush Yadav writel(reg, dphy->regs + DPHY_BAND_CFG);
19641d393aaSPratyush Yadav
19741d393aaSPratyush Yadav /*
19841d393aaSPratyush Yadav * Set the required power island phase 2 time. This is mandated by DPHY
19941d393aaSPratyush Yadav * specs.
20041d393aaSPratyush Yadav */
20141d393aaSPratyush Yadav reg = DPHY_POWER_ISLAND_EN_DATA_VAL;
20241d393aaSPratyush Yadav writel(reg, dphy->regs + DPHY_POWER_ISLAND_EN_DATA);
20341d393aaSPratyush Yadav reg = DPHY_POWER_ISLAND_EN_CLK_VAL;
20441d393aaSPratyush Yadav writel(reg, dphy->regs + DPHY_POWER_ISLAND_EN_CLK);
20541d393aaSPratyush Yadav
20641d393aaSPratyush Yadav ret = cdns_dphy_rx_wait_lane_ready(dphy, lanes);
20741d393aaSPratyush Yadav if (ret) {
20841d393aaSPratyush Yadav dev_err(dphy->dev, "DPHY wait for lane ready timeout\n");
20941d393aaSPratyush Yadav return ret;
21041d393aaSPratyush Yadav }
21141d393aaSPratyush Yadav
21241d393aaSPratyush Yadav return 0;
21341d393aaSPratyush Yadav }
21441d393aaSPratyush Yadav
cdns_dphy_rx_validate(struct phy * phy,enum phy_mode mode,int submode,union phy_configure_opts * opts)21541d393aaSPratyush Yadav static int cdns_dphy_rx_validate(struct phy *phy, enum phy_mode mode,
21641d393aaSPratyush Yadav int submode, union phy_configure_opts *opts)
21741d393aaSPratyush Yadav {
21841d393aaSPratyush Yadav int ret;
21941d393aaSPratyush Yadav
22041d393aaSPratyush Yadav if (mode != PHY_MODE_MIPI_DPHY)
22141d393aaSPratyush Yadav return -EINVAL;
22241d393aaSPratyush Yadav
22341d393aaSPratyush Yadav ret = cdns_dphy_rx_get_band_ctrl(opts->mipi_dphy.hs_clk_rate);
22441d393aaSPratyush Yadav if (ret < 0)
22541d393aaSPratyush Yadav return ret;
22641d393aaSPratyush Yadav
22741d393aaSPratyush Yadav return phy_mipi_dphy_config_validate(&opts->mipi_dphy);
22841d393aaSPratyush Yadav }
22941d393aaSPratyush Yadav
23041d393aaSPratyush Yadav static const struct phy_ops cdns_dphy_rx_ops = {
23141d393aaSPratyush Yadav .power_on = cdns_dphy_rx_power_on,
23241d393aaSPratyush Yadav .power_off = cdns_dphy_rx_power_off,
23341d393aaSPratyush Yadav .configure = cdns_dphy_rx_configure,
23441d393aaSPratyush Yadav .validate = cdns_dphy_rx_validate,
23541d393aaSPratyush Yadav };
23641d393aaSPratyush Yadav
cdns_dphy_rx_probe(struct platform_device * pdev)23741d393aaSPratyush Yadav static int cdns_dphy_rx_probe(struct platform_device *pdev)
23841d393aaSPratyush Yadav {
23941d393aaSPratyush Yadav struct device *dev = &pdev->dev;
24041d393aaSPratyush Yadav struct phy_provider *provider;
24141d393aaSPratyush Yadav struct cdns_dphy_rx *dphy;
24241d393aaSPratyush Yadav
24341d393aaSPratyush Yadav dphy = devm_kzalloc(dev, sizeof(*dphy), GFP_KERNEL);
24441d393aaSPratyush Yadav if (!dphy)
24541d393aaSPratyush Yadav return -ENOMEM;
24641d393aaSPratyush Yadav
24741d393aaSPratyush Yadav dev_set_drvdata(dev, dphy);
24841d393aaSPratyush Yadav dphy->dev = dev;
24941d393aaSPratyush Yadav
25041d393aaSPratyush Yadav dphy->regs = devm_platform_ioremap_resource(pdev, 0);
25141d393aaSPratyush Yadav if (IS_ERR(dphy->regs))
25241d393aaSPratyush Yadav return PTR_ERR(dphy->regs);
25341d393aaSPratyush Yadav
25441d393aaSPratyush Yadav dphy->phy = devm_phy_create(dev, NULL, &cdns_dphy_rx_ops);
25541d393aaSPratyush Yadav if (IS_ERR(dphy->phy)) {
25641d393aaSPratyush Yadav dev_err(dev, "Failed to create PHY: %ld\n", PTR_ERR(dphy->phy));
25741d393aaSPratyush Yadav return PTR_ERR(dphy->phy);
25841d393aaSPratyush Yadav }
25941d393aaSPratyush Yadav
26041d393aaSPratyush Yadav phy_set_drvdata(dphy->phy, dphy);
26141d393aaSPratyush Yadav provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
26241d393aaSPratyush Yadav if (IS_ERR(provider)) {
26341d393aaSPratyush Yadav dev_err(dev, "Failed to register PHY provider: %ld\n",
26441d393aaSPratyush Yadav PTR_ERR(provider));
26541d393aaSPratyush Yadav return PTR_ERR(provider);
26641d393aaSPratyush Yadav }
26741d393aaSPratyush Yadav
26841d393aaSPratyush Yadav return 0;
26941d393aaSPratyush Yadav }
27041d393aaSPratyush Yadav
27141d393aaSPratyush Yadav static const struct of_device_id cdns_dphy_rx_of_match[] = {
27241d393aaSPratyush Yadav { .compatible = "cdns,dphy-rx" },
27341d393aaSPratyush Yadav { /* sentinel */ },
27441d393aaSPratyush Yadav };
27541d393aaSPratyush Yadav MODULE_DEVICE_TABLE(of, cdns_dphy_rx_of_match);
27641d393aaSPratyush Yadav
27741d393aaSPratyush Yadav static struct platform_driver cdns_dphy_rx_platform_driver = {
27841d393aaSPratyush Yadav .probe = cdns_dphy_rx_probe,
27941d393aaSPratyush Yadav .driver = {
28041d393aaSPratyush Yadav .name = "cdns-mipi-dphy-rx",
28141d393aaSPratyush Yadav .of_match_table = cdns_dphy_rx_of_match,
28241d393aaSPratyush Yadav },
28341d393aaSPratyush Yadav };
28441d393aaSPratyush Yadav module_platform_driver(cdns_dphy_rx_platform_driver);
28541d393aaSPratyush Yadav
28641d393aaSPratyush Yadav MODULE_AUTHOR("Pratyush Yadav <p.yadav@ti.com>");
28741d393aaSPratyush Yadav MODULE_DESCRIPTION("Cadence D-PHY Rx Driver");
28841d393aaSPratyush Yadav MODULE_LICENSE("GPL");
289