147e1bb6bSChristian Bruel // SPDX-License-Identifier: GPL-2.0-only 247e1bb6bSChristian Bruel /* 347e1bb6bSChristian Bruel * STMicroelectronics COMBOPHY STM32MP25 Controller driver. 447e1bb6bSChristian Bruel * 547e1bb6bSChristian Bruel * Copyright (C) 2024 STMicroelectronics 647e1bb6bSChristian Bruel * Author: Christian Bruel <christian.bruel@foss.st.com> 747e1bb6bSChristian Bruel */ 847e1bb6bSChristian Bruel 947e1bb6bSChristian Bruel #include <linux/bitfield.h> 1047e1bb6bSChristian Bruel #include <linux/clk.h> 1147e1bb6bSChristian Bruel #include <linux/mfd/syscon.h> 1247e1bb6bSChristian Bruel #include <linux/platform_device.h> 1347e1bb6bSChristian Bruel #include <linux/phy/phy.h> 1447e1bb6bSChristian Bruel #include <linux/pm_runtime.h> 1547e1bb6bSChristian Bruel #include <linux/regmap.h> 1647e1bb6bSChristian Bruel #include <linux/reset.h> 1747e1bb6bSChristian Bruel #include <dt-bindings/phy/phy.h> 1847e1bb6bSChristian Bruel 1947e1bb6bSChristian Bruel #define SYSCFG_COMBOPHY_CR1 0x4c00 2047e1bb6bSChristian Bruel #define SYSCFG_COMBOPHY_CR2 0x4c04 2147e1bb6bSChristian Bruel #define SYSCFG_COMBOPHY_CR4 0x4c0c 2247e1bb6bSChristian Bruel #define SYSCFG_COMBOPHY_CR5 0x4c10 2347e1bb6bSChristian Bruel #define SYSCFG_COMBOPHY_SR 0x4c14 2447e1bb6bSChristian Bruel #define SYSCFG_PCIEPRGCR 0x6080 2547e1bb6bSChristian Bruel 2647e1bb6bSChristian Bruel /* SYSCFG PCIEPRGCR */ 2747e1bb6bSChristian Bruel #define STM32MP25_PCIEPRGCR_EN BIT(0) 2847e1bb6bSChristian Bruel #define STM32MP25_PCIEPRG_IMPCTRL_OHM GENMASK(3, 1) 2947e1bb6bSChristian Bruel #define STM32MP25_PCIEPRG_IMPCTRL_VSWING GENMASK(5, 4) 3047e1bb6bSChristian Bruel 3147e1bb6bSChristian Bruel /* SYSCFG SYSCFG_COMBOPHY_SR */ 3247e1bb6bSChristian Bruel #define STM32MP25_PIPE0_PHYSTATUS BIT(1) 3347e1bb6bSChristian Bruel 3447e1bb6bSChristian Bruel /* SYSCFG CR1 */ 3547e1bb6bSChristian Bruel #define SYSCFG_COMBOPHY_CR1_REFUSEPAD BIT(0) 3647e1bb6bSChristian Bruel #define SYSCFG_COMBOPHY_CR1_MPLLMULT GENMASK(7, 1) 3747e1bb6bSChristian Bruel #define SYSCFG_COMBOPHY_CR1_REFCLKSEL GENMASK(16, 8) 3847e1bb6bSChristian Bruel #define SYSCFG_COMBOPHY_CR1_REFCLKDIV2 BIT(17) 3947e1bb6bSChristian Bruel #define SYSCFG_COMBOPHY_CR1_REFSSPEN BIT(18) 4047e1bb6bSChristian Bruel #define SYSCFG_COMBOPHY_CR1_SSCEN BIT(19) 4147e1bb6bSChristian Bruel 4247e1bb6bSChristian Bruel /* SYSCFG CR4 */ 4347e1bb6bSChristian Bruel #define SYSCFG_COMBOPHY_CR4_RX0_EQ GENMASK(2, 0) 4447e1bb6bSChristian Bruel 4547e1bb6bSChristian Bruel #define MPLLMULT_19_2 (0x02u << 1) 4647e1bb6bSChristian Bruel #define MPLLMULT_20 (0x7du << 1) 4747e1bb6bSChristian Bruel #define MPLLMULT_24 (0x68u << 1) 4847e1bb6bSChristian Bruel #define MPLLMULT_25 (0x64u << 1) 4947e1bb6bSChristian Bruel #define MPLLMULT_26 (0x60u << 1) 5047e1bb6bSChristian Bruel #define MPLLMULT_38_4 (0x41u << 1) 5147e1bb6bSChristian Bruel #define MPLLMULT_48 (0x6cu << 1) 5247e1bb6bSChristian Bruel #define MPLLMULT_50 (0x32u << 1) 5347e1bb6bSChristian Bruel #define MPLLMULT_52 (0x30u << 1) 5447e1bb6bSChristian Bruel #define MPLLMULT_100 (0x19u << 1) 5547e1bb6bSChristian Bruel 5647e1bb6bSChristian Bruel #define REFCLKSEL_0 0 5747e1bb6bSChristian Bruel #define REFCLKSEL_1 (0x108u << 8) 5847e1bb6bSChristian Bruel 5947e1bb6bSChristian Bruel #define REFCLDIV_0 0 6047e1bb6bSChristian Bruel 6147e1bb6bSChristian Bruel /* SYSCFG CR2 */ 6247e1bb6bSChristian Bruel #define SYSCFG_COMBOPHY_CR2_MODESEL GENMASK(1, 0) 6347e1bb6bSChristian Bruel #define SYSCFG_COMBOPHY_CR2_ISO_DIS BIT(15) 6447e1bb6bSChristian Bruel 6547e1bb6bSChristian Bruel #define COMBOPHY_MODESEL_PCIE 0 6647e1bb6bSChristian Bruel #define COMBOPHY_MODESEL_USB 3 6747e1bb6bSChristian Bruel 6847e1bb6bSChristian Bruel /* SYSCFG CR5 */ 6947e1bb6bSChristian Bruel #define SYSCFG_COMBOPHY_CR5_COMMON_CLOCKS BIT(12) 7047e1bb6bSChristian Bruel 7147e1bb6bSChristian Bruel #define COMBOPHY_SUP_ANA_MPLL_LOOP_CTL 0xc0 7247e1bb6bSChristian Bruel #define COMBOPHY_PROP_CNTRL GENMASK(7, 4) 7347e1bb6bSChristian Bruel 7447e1bb6bSChristian Bruel /* Required apb/ker clocks first, optional pad last. */ 7547e1bb6bSChristian Bruel static const char * const combophy_clks[] = {"apb", "ker", "pad"}; 7647e1bb6bSChristian Bruel #define APB_CLK 0 7747e1bb6bSChristian Bruel #define KER_CLK 1 7847e1bb6bSChristian Bruel #define PAD_CLK 2 7947e1bb6bSChristian Bruel 8047e1bb6bSChristian Bruel struct stm32_combophy { 8147e1bb6bSChristian Bruel struct phy *phy; 8247e1bb6bSChristian Bruel struct regmap *regmap; 8347e1bb6bSChristian Bruel struct device *dev; 8447e1bb6bSChristian Bruel void __iomem *base; 8547e1bb6bSChristian Bruel struct reset_control *phy_reset; 8647e1bb6bSChristian Bruel struct clk_bulk_data clks[ARRAY_SIZE(combophy_clks)]; 8747e1bb6bSChristian Bruel int num_clks; 8847e1bb6bSChristian Bruel bool have_pad_clk; 8947e1bb6bSChristian Bruel unsigned int type; 9047e1bb6bSChristian Bruel bool is_init; 9147e1bb6bSChristian Bruel int irq_wakeup; 9247e1bb6bSChristian Bruel }; 9347e1bb6bSChristian Bruel 9447e1bb6bSChristian Bruel struct clk_impedance { 9547e1bb6bSChristian Bruel u32 microohm; 9647e1bb6bSChristian Bruel u32 vswing[4]; 9747e1bb6bSChristian Bruel }; 9847e1bb6bSChristian Bruel 9947e1bb6bSChristian Bruel /* 10047e1bb6bSChristian Bruel * lookup table to hold the settings needed for a ref clock frequency 10147e1bb6bSChristian Bruel * impedance, the offset is used to set the IMP_CTL and DE_EMP bit of the 10247e1bb6bSChristian Bruel * PRG_IMP_CTRL register. Use ordered discrete values in the table 10347e1bb6bSChristian Bruel */ 10447e1bb6bSChristian Bruel static const struct clk_impedance imp_lookup[] = { 10547e1bb6bSChristian Bruel { 6090000, { 442000, 564000, 684000, 802000 } }, 10647e1bb6bSChristian Bruel { 5662000, { 528000, 621000, 712000, 803000 } }, 10747e1bb6bSChristian Bruel { 5292000, { 491000, 596000, 700000, 802000 } }, 10847e1bb6bSChristian Bruel { 4968000, { 558000, 640000, 722000, 803000 } }, 10947e1bb6bSChristian Bruel { 4684000, { 468000, 581000, 692000, 802000 } }, 11047e1bb6bSChristian Bruel { 4429000, { 554000, 613000, 717000, 803000 } }, 11147e1bb6bSChristian Bruel { 4204000, { 511000, 609000, 706000, 802000 } }, 11247e1bb6bSChristian Bruel { 3999000, { 571000, 648000, 726000, 803000 } } 11347e1bb6bSChristian Bruel }; 11447e1bb6bSChristian Bruel 11547e1bb6bSChristian Bruel static int stm32_impedance_tune(struct stm32_combophy *combophy) 11647e1bb6bSChristian Bruel { 11747e1bb6bSChristian Bruel u8 imp_size = ARRAY_SIZE(imp_lookup); 11847e1bb6bSChristian Bruel u8 vswing_size = ARRAY_SIZE(imp_lookup[0].vswing); 11947e1bb6bSChristian Bruel u8 imp_of, vswing_of; 12047e1bb6bSChristian Bruel u32 max_imp = imp_lookup[0].microohm; 12147e1bb6bSChristian Bruel u32 min_imp = imp_lookup[imp_size - 1].microohm; 12247e1bb6bSChristian Bruel u32 max_vswing = imp_lookup[imp_size - 1].vswing[vswing_size - 1]; 12347e1bb6bSChristian Bruel u32 min_vswing = imp_lookup[0].vswing[0]; 12447e1bb6bSChristian Bruel u32 val; 125*2de679ecSArnd Bergmann u32 regval; 12647e1bb6bSChristian Bruel 12747e1bb6bSChristian Bruel if (!of_property_read_u32(combophy->dev->of_node, "st,output-micro-ohms", &val)) { 12847e1bb6bSChristian Bruel if (val < min_imp || val > max_imp) { 12947e1bb6bSChristian Bruel dev_err(combophy->dev, "Invalid value %u for output ohm\n", val); 13047e1bb6bSChristian Bruel return -EINVAL; 13147e1bb6bSChristian Bruel } 13247e1bb6bSChristian Bruel 133*2de679ecSArnd Bergmann regval = 0; 134*2de679ecSArnd Bergmann for (imp_of = 0; imp_of < ARRAY_SIZE(imp_lookup); imp_of++) { 135*2de679ecSArnd Bergmann if (imp_lookup[imp_of].microohm <= val) { 136*2de679ecSArnd Bergmann regval = FIELD_PREP(STM32MP25_PCIEPRG_IMPCTRL_OHM, imp_of); 13747e1bb6bSChristian Bruel break; 138*2de679ecSArnd Bergmann } 139*2de679ecSArnd Bergmann } 14047e1bb6bSChristian Bruel 14147e1bb6bSChristian Bruel dev_dbg(combophy->dev, "Set %u micro-ohms output impedance\n", 14247e1bb6bSChristian Bruel imp_lookup[imp_of].microohm); 14347e1bb6bSChristian Bruel 14447e1bb6bSChristian Bruel regmap_update_bits(combophy->regmap, SYSCFG_PCIEPRGCR, 14547e1bb6bSChristian Bruel STM32MP25_PCIEPRG_IMPCTRL_OHM, 146*2de679ecSArnd Bergmann regval); 14747e1bb6bSChristian Bruel } else { 14847e1bb6bSChristian Bruel regmap_read(combophy->regmap, SYSCFG_PCIEPRGCR, &val); 14947e1bb6bSChristian Bruel imp_of = FIELD_GET(STM32MP25_PCIEPRG_IMPCTRL_OHM, val); 15047e1bb6bSChristian Bruel } 15147e1bb6bSChristian Bruel 15247e1bb6bSChristian Bruel if (!of_property_read_u32(combophy->dev->of_node, "st,output-vswing-microvolt", &val)) { 15347e1bb6bSChristian Bruel if (val < min_vswing || val > max_vswing) { 15447e1bb6bSChristian Bruel dev_err(combophy->dev, "Invalid value %u for output vswing\n", val); 15547e1bb6bSChristian Bruel return -EINVAL; 15647e1bb6bSChristian Bruel } 15747e1bb6bSChristian Bruel 158*2de679ecSArnd Bergmann regval = 0; 159*2de679ecSArnd Bergmann for (vswing_of = 0; vswing_of < ARRAY_SIZE(imp_lookup[imp_of].vswing); vswing_of++) { 160*2de679ecSArnd Bergmann if (imp_lookup[imp_of].vswing[vswing_of] >= val) { 161*2de679ecSArnd Bergmann regval = FIELD_PREP(STM32MP25_PCIEPRG_IMPCTRL_VSWING, vswing_of); 16247e1bb6bSChristian Bruel break; 163*2de679ecSArnd Bergmann } 164*2de679ecSArnd Bergmann } 16547e1bb6bSChristian Bruel 16647e1bb6bSChristian Bruel dev_dbg(combophy->dev, "Set %u microvolt swing\n", 16747e1bb6bSChristian Bruel imp_lookup[imp_of].vswing[vswing_of]); 16847e1bb6bSChristian Bruel 16947e1bb6bSChristian Bruel regmap_update_bits(combophy->regmap, SYSCFG_PCIEPRGCR, 17047e1bb6bSChristian Bruel STM32MP25_PCIEPRG_IMPCTRL_VSWING, 171*2de679ecSArnd Bergmann regval); 17247e1bb6bSChristian Bruel } 17347e1bb6bSChristian Bruel 17447e1bb6bSChristian Bruel return 0; 17547e1bb6bSChristian Bruel } 17647e1bb6bSChristian Bruel 17747e1bb6bSChristian Bruel static int stm32_combophy_pll_init(struct stm32_combophy *combophy) 17847e1bb6bSChristian Bruel { 17947e1bb6bSChristian Bruel int ret; 18047e1bb6bSChristian Bruel u32 refclksel, pllmult, propcntrl, val; 18147e1bb6bSChristian Bruel u32 clk_rate; 18247e1bb6bSChristian Bruel struct clk *clk; 18347e1bb6bSChristian Bruel u32 cr1_val = 0, cr1_mask = 0; 18447e1bb6bSChristian Bruel 18547e1bb6bSChristian Bruel if (combophy->have_pad_clk) 18647e1bb6bSChristian Bruel clk = combophy->clks[PAD_CLK].clk; 18747e1bb6bSChristian Bruel else 18847e1bb6bSChristian Bruel clk = combophy->clks[KER_CLK].clk; 18947e1bb6bSChristian Bruel 19047e1bb6bSChristian Bruel clk_rate = clk_get_rate(clk); 19147e1bb6bSChristian Bruel 19247e1bb6bSChristian Bruel dev_dbg(combophy->dev, "%s pll init rate %d\n", 19347e1bb6bSChristian Bruel combophy->have_pad_clk ? "External" : "Ker", clk_rate); 19447e1bb6bSChristian Bruel 19547e1bb6bSChristian Bruel if (combophy->type != PHY_TYPE_PCIE) { 19647e1bb6bSChristian Bruel cr1_mask |= SYSCFG_COMBOPHY_CR1_REFSSPEN; 19747e1bb6bSChristian Bruel cr1_val |= SYSCFG_COMBOPHY_CR1_REFSSPEN; 19847e1bb6bSChristian Bruel } 19947e1bb6bSChristian Bruel 20047e1bb6bSChristian Bruel if (of_property_present(combophy->dev->of_node, "st,ssc-on")) { 20147e1bb6bSChristian Bruel dev_dbg(combophy->dev, "Enabling clock with SSC\n"); 20247e1bb6bSChristian Bruel cr1_mask |= SYSCFG_COMBOPHY_CR1_SSCEN; 20347e1bb6bSChristian Bruel cr1_val |= SYSCFG_COMBOPHY_CR1_SSCEN; 20447e1bb6bSChristian Bruel } 20547e1bb6bSChristian Bruel 20647e1bb6bSChristian Bruel switch (clk_rate) { 20747e1bb6bSChristian Bruel case 100000000: 20847e1bb6bSChristian Bruel pllmult = MPLLMULT_100; 20947e1bb6bSChristian Bruel refclksel = REFCLKSEL_0; 21047e1bb6bSChristian Bruel propcntrl = 0x8u << 4; 21147e1bb6bSChristian Bruel break; 21247e1bb6bSChristian Bruel case 19200000: 21347e1bb6bSChristian Bruel pllmult = MPLLMULT_19_2; 21447e1bb6bSChristian Bruel refclksel = REFCLKSEL_1; 21547e1bb6bSChristian Bruel propcntrl = 0x8u << 4; 21647e1bb6bSChristian Bruel break; 21747e1bb6bSChristian Bruel case 25000000: 21847e1bb6bSChristian Bruel pllmult = MPLLMULT_25; 21947e1bb6bSChristian Bruel refclksel = REFCLKSEL_0; 22047e1bb6bSChristian Bruel propcntrl = 0xeu << 4; 22147e1bb6bSChristian Bruel break; 22247e1bb6bSChristian Bruel case 24000000: 22347e1bb6bSChristian Bruel pllmult = MPLLMULT_24; 22447e1bb6bSChristian Bruel refclksel = REFCLKSEL_1; 22547e1bb6bSChristian Bruel propcntrl = 0xeu << 4; 22647e1bb6bSChristian Bruel break; 22747e1bb6bSChristian Bruel case 20000000: 22847e1bb6bSChristian Bruel pllmult = MPLLMULT_20; 22947e1bb6bSChristian Bruel refclksel = REFCLKSEL_0; 23047e1bb6bSChristian Bruel propcntrl = 0xeu << 4; 23147e1bb6bSChristian Bruel break; 23247e1bb6bSChristian Bruel default: 23347e1bb6bSChristian Bruel dev_err(combophy->dev, "Invalid rate 0x%x\n", clk_rate); 23447e1bb6bSChristian Bruel return -EINVAL; 235e592a655SYang Li } 23647e1bb6bSChristian Bruel 23747e1bb6bSChristian Bruel cr1_mask |= SYSCFG_COMBOPHY_CR1_REFCLKDIV2; 23847e1bb6bSChristian Bruel cr1_val |= REFCLDIV_0; 23947e1bb6bSChristian Bruel 24047e1bb6bSChristian Bruel cr1_mask |= SYSCFG_COMBOPHY_CR1_REFCLKSEL; 24147e1bb6bSChristian Bruel cr1_val |= refclksel; 24247e1bb6bSChristian Bruel 24347e1bb6bSChristian Bruel cr1_mask |= SYSCFG_COMBOPHY_CR1_MPLLMULT; 24447e1bb6bSChristian Bruel cr1_val |= pllmult; 24547e1bb6bSChristian Bruel 24647e1bb6bSChristian Bruel /* 24747e1bb6bSChristian Bruel * vddcombophy is interconnected with vddcore. Isolation bit should be unset 24847e1bb6bSChristian Bruel * before using the ComboPHY. 24947e1bb6bSChristian Bruel */ 25047e1bb6bSChristian Bruel regmap_update_bits(combophy->regmap, SYSCFG_COMBOPHY_CR2, 25147e1bb6bSChristian Bruel SYSCFG_COMBOPHY_CR2_ISO_DIS, SYSCFG_COMBOPHY_CR2_ISO_DIS); 25247e1bb6bSChristian Bruel 25347e1bb6bSChristian Bruel reset_control_assert(combophy->phy_reset); 25447e1bb6bSChristian Bruel 25547e1bb6bSChristian Bruel if (combophy->type == PHY_TYPE_PCIE) { 25647e1bb6bSChristian Bruel ret = stm32_impedance_tune(combophy); 25747e1bb6bSChristian Bruel if (ret) 25847e1bb6bSChristian Bruel goto out_iso; 25947e1bb6bSChristian Bruel 26047e1bb6bSChristian Bruel cr1_mask |= SYSCFG_COMBOPHY_CR1_REFUSEPAD; 26147e1bb6bSChristian Bruel cr1_val |= combophy->have_pad_clk ? SYSCFG_COMBOPHY_CR1_REFUSEPAD : 0; 26247e1bb6bSChristian Bruel } 26347e1bb6bSChristian Bruel 26447e1bb6bSChristian Bruel if (!of_property_read_u32(combophy->dev->of_node, "st,rx-equalizer", &val)) { 26547e1bb6bSChristian Bruel dev_dbg(combophy->dev, "Set RX equalizer %u\n", val); 26647e1bb6bSChristian Bruel if (val > SYSCFG_COMBOPHY_CR4_RX0_EQ) { 26747e1bb6bSChristian Bruel dev_err(combophy->dev, "Invalid value %u for rx0 equalizer\n", val); 26847e1bb6bSChristian Bruel ret = -EINVAL; 26947e1bb6bSChristian Bruel goto out_iso; 27047e1bb6bSChristian Bruel } 27147e1bb6bSChristian Bruel 27247e1bb6bSChristian Bruel regmap_update_bits(combophy->regmap, SYSCFG_COMBOPHY_CR4, 27347e1bb6bSChristian Bruel SYSCFG_COMBOPHY_CR4_RX0_EQ, val); 27447e1bb6bSChristian Bruel } 27547e1bb6bSChristian Bruel 27647e1bb6bSChristian Bruel regmap_update_bits(combophy->regmap, SYSCFG_COMBOPHY_CR1, cr1_mask, cr1_val); 27747e1bb6bSChristian Bruel 27847e1bb6bSChristian Bruel /* 27947e1bb6bSChristian Bruel * Force elasticity buffer to be tuned for the reference clock as 28047e1bb6bSChristian Bruel * the separated clock model is not supported 28147e1bb6bSChristian Bruel */ 28247e1bb6bSChristian Bruel regmap_update_bits(combophy->regmap, SYSCFG_COMBOPHY_CR5, 28347e1bb6bSChristian Bruel SYSCFG_COMBOPHY_CR5_COMMON_CLOCKS, SYSCFG_COMBOPHY_CR5_COMMON_CLOCKS); 28447e1bb6bSChristian Bruel 28547e1bb6bSChristian Bruel reset_control_deassert(combophy->phy_reset); 28647e1bb6bSChristian Bruel 28747e1bb6bSChristian Bruel ret = regmap_read_poll_timeout(combophy->regmap, SYSCFG_COMBOPHY_SR, val, 28847e1bb6bSChristian Bruel !(val & STM32MP25_PIPE0_PHYSTATUS), 28947e1bb6bSChristian Bruel 10, 1000); 29047e1bb6bSChristian Bruel if (ret) { 29147e1bb6bSChristian Bruel dev_err(combophy->dev, "timeout, cannot lock PLL\n"); 29247e1bb6bSChristian Bruel if (combophy->type == PHY_TYPE_PCIE && !combophy->have_pad_clk) 29347e1bb6bSChristian Bruel regmap_update_bits(combophy->regmap, SYSCFG_PCIEPRGCR, 29447e1bb6bSChristian Bruel STM32MP25_PCIEPRGCR_EN, 0); 29547e1bb6bSChristian Bruel 29647e1bb6bSChristian Bruel if (combophy->type != PHY_TYPE_PCIE) 29747e1bb6bSChristian Bruel regmap_update_bits(combophy->regmap, SYSCFG_COMBOPHY_CR1, 29847e1bb6bSChristian Bruel SYSCFG_COMBOPHY_CR1_REFSSPEN, 0); 29947e1bb6bSChristian Bruel 30047e1bb6bSChristian Bruel goto out; 30147e1bb6bSChristian Bruel } 30247e1bb6bSChristian Bruel 30347e1bb6bSChristian Bruel 30447e1bb6bSChristian Bruel if (combophy->type == PHY_TYPE_PCIE) { 30547e1bb6bSChristian Bruel if (!combophy->have_pad_clk) 30647e1bb6bSChristian Bruel regmap_update_bits(combophy->regmap, SYSCFG_PCIEPRGCR, 30747e1bb6bSChristian Bruel STM32MP25_PCIEPRGCR_EN, STM32MP25_PCIEPRGCR_EN); 30847e1bb6bSChristian Bruel 30947e1bb6bSChristian Bruel val = readl_relaxed(combophy->base + COMBOPHY_SUP_ANA_MPLL_LOOP_CTL); 31047e1bb6bSChristian Bruel val &= ~COMBOPHY_PROP_CNTRL; 31147e1bb6bSChristian Bruel val |= propcntrl; 31247e1bb6bSChristian Bruel writel_relaxed(val, combophy->base + COMBOPHY_SUP_ANA_MPLL_LOOP_CTL); 31347e1bb6bSChristian Bruel } 31447e1bb6bSChristian Bruel 31547e1bb6bSChristian Bruel return 0; 31647e1bb6bSChristian Bruel 31747e1bb6bSChristian Bruel out_iso: 31847e1bb6bSChristian Bruel reset_control_deassert(combophy->phy_reset); 31947e1bb6bSChristian Bruel 32047e1bb6bSChristian Bruel out: 32147e1bb6bSChristian Bruel regmap_update_bits(combophy->regmap, SYSCFG_COMBOPHY_CR2, 32247e1bb6bSChristian Bruel SYSCFG_COMBOPHY_CR2_ISO_DIS, 0); 32347e1bb6bSChristian Bruel 32447e1bb6bSChristian Bruel return ret; 32547e1bb6bSChristian Bruel } 32647e1bb6bSChristian Bruel 32747e1bb6bSChristian Bruel static struct phy *stm32_combophy_xlate(struct device *dev, 32847e1bb6bSChristian Bruel const struct of_phandle_args *args) 32947e1bb6bSChristian Bruel { 33047e1bb6bSChristian Bruel struct stm32_combophy *combophy = dev_get_drvdata(dev); 33147e1bb6bSChristian Bruel unsigned int type; 33247e1bb6bSChristian Bruel 33347e1bb6bSChristian Bruel if (args->args_count != 1) { 33447e1bb6bSChristian Bruel dev_err(dev, "invalid number of cells in 'phy' property\n"); 33547e1bb6bSChristian Bruel return ERR_PTR(-EINVAL); 33647e1bb6bSChristian Bruel } 33747e1bb6bSChristian Bruel 33847e1bb6bSChristian Bruel type = args->args[0]; 33947e1bb6bSChristian Bruel if (type != PHY_TYPE_USB3 && type != PHY_TYPE_PCIE) { 34047e1bb6bSChristian Bruel dev_err(dev, "unsupported device type: %d\n", type); 34147e1bb6bSChristian Bruel return ERR_PTR(-EINVAL); 34247e1bb6bSChristian Bruel } 34347e1bb6bSChristian Bruel 34447e1bb6bSChristian Bruel if (combophy->have_pad_clk && type != PHY_TYPE_PCIE) { 34547e1bb6bSChristian Bruel dev_err(dev, "Invalid use of clk_pad for USB3 mode\n"); 34647e1bb6bSChristian Bruel return ERR_PTR(-EINVAL); 34747e1bb6bSChristian Bruel } 34847e1bb6bSChristian Bruel 34947e1bb6bSChristian Bruel combophy->type = type; 35047e1bb6bSChristian Bruel 35147e1bb6bSChristian Bruel return combophy->phy; 35247e1bb6bSChristian Bruel } 35347e1bb6bSChristian Bruel 35447e1bb6bSChristian Bruel static int stm32_combophy_set_mode(struct stm32_combophy *combophy) 35547e1bb6bSChristian Bruel { 35647e1bb6bSChristian Bruel int type = combophy->type; 35747e1bb6bSChristian Bruel u32 val; 35847e1bb6bSChristian Bruel 35947e1bb6bSChristian Bruel switch (type) { 36047e1bb6bSChristian Bruel case PHY_TYPE_PCIE: 36147e1bb6bSChristian Bruel dev_dbg(combophy->dev, "setting PCIe ComboPHY\n"); 36247e1bb6bSChristian Bruel val = COMBOPHY_MODESEL_PCIE; 36347e1bb6bSChristian Bruel break; 36447e1bb6bSChristian Bruel case PHY_TYPE_USB3: 36547e1bb6bSChristian Bruel dev_dbg(combophy->dev, "setting USB3 ComboPHY\n"); 36647e1bb6bSChristian Bruel val = COMBOPHY_MODESEL_USB; 36747e1bb6bSChristian Bruel break; 36847e1bb6bSChristian Bruel default: 36947e1bb6bSChristian Bruel dev_err(combophy->dev, "Invalid PHY mode %d\n", type); 37047e1bb6bSChristian Bruel return -EINVAL; 37147e1bb6bSChristian Bruel } 37247e1bb6bSChristian Bruel 37347e1bb6bSChristian Bruel return regmap_update_bits(combophy->regmap, SYSCFG_COMBOPHY_CR2, 37447e1bb6bSChristian Bruel SYSCFG_COMBOPHY_CR2_MODESEL, val); 37547e1bb6bSChristian Bruel } 37647e1bb6bSChristian Bruel 37747e1bb6bSChristian Bruel static int stm32_combophy_suspend_noirq(struct device *dev) 37847e1bb6bSChristian Bruel { 37947e1bb6bSChristian Bruel struct stm32_combophy *combophy = dev_get_drvdata(dev); 38047e1bb6bSChristian Bruel 38147e1bb6bSChristian Bruel /* 38247e1bb6bSChristian Bruel * Clocks should be turned off since it is not needed for 38347e1bb6bSChristian Bruel * wakeup capability. In case usb-remote wakeup is not enabled, 38447e1bb6bSChristian Bruel * combo-phy is already turned off by HCD driver using exit callback 38547e1bb6bSChristian Bruel */ 38647e1bb6bSChristian Bruel if (combophy->is_init) { 38747e1bb6bSChristian Bruel clk_bulk_disable_unprepare(combophy->num_clks, combophy->clks); 38847e1bb6bSChristian Bruel 38947e1bb6bSChristian Bruel /* since wakeup is enabled for ctrl */ 39047e1bb6bSChristian Bruel enable_irq_wake(combophy->irq_wakeup); 39147e1bb6bSChristian Bruel } 39247e1bb6bSChristian Bruel 39347e1bb6bSChristian Bruel return 0; 39447e1bb6bSChristian Bruel } 39547e1bb6bSChristian Bruel 39647e1bb6bSChristian Bruel static int stm32_combophy_resume_noirq(struct device *dev) 39747e1bb6bSChristian Bruel { 39847e1bb6bSChristian Bruel struct stm32_combophy *combophy = dev_get_drvdata(dev); 39947e1bb6bSChristian Bruel int ret; 40047e1bb6bSChristian Bruel 40147e1bb6bSChristian Bruel /* 40247e1bb6bSChristian Bruel * If clocks was turned off by suspend call for wakeup then needs 40347e1bb6bSChristian Bruel * to be turned back ON in resume. In case usb-remote wakeup is not 40447e1bb6bSChristian Bruel * enabled, clocks already turned ON by HCD driver using init callback 40547e1bb6bSChristian Bruel */ 40647e1bb6bSChristian Bruel if (combophy->is_init) { 40747e1bb6bSChristian Bruel /* since wakeup was enabled for ctrl */ 40847e1bb6bSChristian Bruel disable_irq_wake(combophy->irq_wakeup); 40947e1bb6bSChristian Bruel 41047e1bb6bSChristian Bruel ret = clk_bulk_prepare_enable(combophy->num_clks, combophy->clks); 41147e1bb6bSChristian Bruel if (ret) { 41247e1bb6bSChristian Bruel dev_err(dev, "can't enable clocks (%d)\n", ret); 41347e1bb6bSChristian Bruel return ret; 41447e1bb6bSChristian Bruel } 41547e1bb6bSChristian Bruel } 41647e1bb6bSChristian Bruel 41747e1bb6bSChristian Bruel return 0; 41847e1bb6bSChristian Bruel } 41947e1bb6bSChristian Bruel 42047e1bb6bSChristian Bruel static int stm32_combophy_exit(struct phy *phy) 42147e1bb6bSChristian Bruel { 42247e1bb6bSChristian Bruel struct stm32_combophy *combophy = phy_get_drvdata(phy); 42347e1bb6bSChristian Bruel struct device *dev = combophy->dev; 42447e1bb6bSChristian Bruel 42547e1bb6bSChristian Bruel combophy->is_init = false; 42647e1bb6bSChristian Bruel 42747e1bb6bSChristian Bruel if (combophy->type == PHY_TYPE_PCIE && !combophy->have_pad_clk) 42847e1bb6bSChristian Bruel regmap_update_bits(combophy->regmap, SYSCFG_PCIEPRGCR, 42947e1bb6bSChristian Bruel STM32MP25_PCIEPRGCR_EN, 0); 43047e1bb6bSChristian Bruel 43147e1bb6bSChristian Bruel if (combophy->type != PHY_TYPE_PCIE) 43247e1bb6bSChristian Bruel regmap_update_bits(combophy->regmap, SYSCFG_COMBOPHY_CR1, 43347e1bb6bSChristian Bruel SYSCFG_COMBOPHY_CR1_REFSSPEN, 0); 43447e1bb6bSChristian Bruel 43547e1bb6bSChristian Bruel regmap_update_bits(combophy->regmap, SYSCFG_COMBOPHY_CR2, 43647e1bb6bSChristian Bruel SYSCFG_COMBOPHY_CR2_ISO_DIS, 0); 43747e1bb6bSChristian Bruel 43847e1bb6bSChristian Bruel clk_bulk_disable_unprepare(combophy->num_clks, combophy->clks); 43947e1bb6bSChristian Bruel 44047e1bb6bSChristian Bruel pm_runtime_put_noidle(dev); 44147e1bb6bSChristian Bruel 44247e1bb6bSChristian Bruel return 0; 44347e1bb6bSChristian Bruel } 44447e1bb6bSChristian Bruel 44547e1bb6bSChristian Bruel static int stm32_combophy_init(struct phy *phy) 44647e1bb6bSChristian Bruel { 44747e1bb6bSChristian Bruel struct stm32_combophy *combophy = phy_get_drvdata(phy); 44847e1bb6bSChristian Bruel struct device *dev = combophy->dev; 44947e1bb6bSChristian Bruel int ret; 45047e1bb6bSChristian Bruel 45147e1bb6bSChristian Bruel pm_runtime_get_noresume(dev); 45247e1bb6bSChristian Bruel 45347e1bb6bSChristian Bruel ret = clk_bulk_prepare_enable(combophy->num_clks, combophy->clks); 45447e1bb6bSChristian Bruel if (ret) { 45547e1bb6bSChristian Bruel dev_err(dev, "can't enable clocks (%d)\n", ret); 45647e1bb6bSChristian Bruel pm_runtime_put_noidle(dev); 45747e1bb6bSChristian Bruel return ret; 45847e1bb6bSChristian Bruel } 45947e1bb6bSChristian Bruel 46047e1bb6bSChristian Bruel ret = stm32_combophy_set_mode(combophy); 46147e1bb6bSChristian Bruel if (ret) { 46247e1bb6bSChristian Bruel dev_err(dev, "combophy mode not set\n"); 46347e1bb6bSChristian Bruel clk_bulk_disable_unprepare(combophy->num_clks, combophy->clks); 46447e1bb6bSChristian Bruel pm_runtime_put_noidle(dev); 46547e1bb6bSChristian Bruel return ret; 46647e1bb6bSChristian Bruel } 46747e1bb6bSChristian Bruel 46847e1bb6bSChristian Bruel ret = stm32_combophy_pll_init(combophy); 46947e1bb6bSChristian Bruel if (ret) { 47047e1bb6bSChristian Bruel clk_bulk_disable_unprepare(combophy->num_clks, combophy->clks); 47147e1bb6bSChristian Bruel pm_runtime_put_noidle(dev); 47247e1bb6bSChristian Bruel return ret; 47347e1bb6bSChristian Bruel } 47447e1bb6bSChristian Bruel 47547e1bb6bSChristian Bruel pm_runtime_disable(dev); 47647e1bb6bSChristian Bruel pm_runtime_set_active(dev); 47747e1bb6bSChristian Bruel pm_runtime_enable(dev); 47847e1bb6bSChristian Bruel 47947e1bb6bSChristian Bruel combophy->is_init = true; 48047e1bb6bSChristian Bruel 48147e1bb6bSChristian Bruel return ret; 48247e1bb6bSChristian Bruel } 48347e1bb6bSChristian Bruel 48447e1bb6bSChristian Bruel static const struct phy_ops stm32_combophy_phy_data = { 48547e1bb6bSChristian Bruel .init = stm32_combophy_init, 48647e1bb6bSChristian Bruel .exit = stm32_combophy_exit, 48747e1bb6bSChristian Bruel .owner = THIS_MODULE 48847e1bb6bSChristian Bruel }; 48947e1bb6bSChristian Bruel 49047e1bb6bSChristian Bruel static irqreturn_t stm32_combophy_irq_wakeup_handler(int irq, void *dev_id) 49147e1bb6bSChristian Bruel { 49247e1bb6bSChristian Bruel return IRQ_HANDLED; 49347e1bb6bSChristian Bruel } 49447e1bb6bSChristian Bruel 49547e1bb6bSChristian Bruel static int stm32_combophy_get_clocks(struct stm32_combophy *combophy) 49647e1bb6bSChristian Bruel { 49747e1bb6bSChristian Bruel int i, ret; 49847e1bb6bSChristian Bruel 49947e1bb6bSChristian Bruel for (i = 0; i < ARRAY_SIZE(combophy_clks); i++) 50047e1bb6bSChristian Bruel combophy->clks[i].id = combophy_clks[i]; 50147e1bb6bSChristian Bruel 50247e1bb6bSChristian Bruel combophy->num_clks = ARRAY_SIZE(combophy_clks) - 1; 50347e1bb6bSChristian Bruel 50447e1bb6bSChristian Bruel ret = devm_clk_bulk_get(combophy->dev, combophy->num_clks, combophy->clks); 50547e1bb6bSChristian Bruel if (ret) 50647e1bb6bSChristian Bruel return ret; 50747e1bb6bSChristian Bruel 50847e1bb6bSChristian Bruel ret = devm_clk_bulk_get_optional(combophy->dev, 1, combophy->clks + combophy->num_clks); 50947e1bb6bSChristian Bruel if (ret) 51047e1bb6bSChristian Bruel return ret; 51147e1bb6bSChristian Bruel 51247e1bb6bSChristian Bruel if (combophy->clks[combophy->num_clks].clk != NULL) { 51347e1bb6bSChristian Bruel combophy->have_pad_clk = true; 51447e1bb6bSChristian Bruel combophy->num_clks++; 51547e1bb6bSChristian Bruel } 51647e1bb6bSChristian Bruel 51747e1bb6bSChristian Bruel return 0; 51847e1bb6bSChristian Bruel } 51947e1bb6bSChristian Bruel 52047e1bb6bSChristian Bruel static int stm32_combophy_probe(struct platform_device *pdev) 52147e1bb6bSChristian Bruel { 52247e1bb6bSChristian Bruel struct stm32_combophy *combophy; 52347e1bb6bSChristian Bruel struct device *dev = &pdev->dev; 52447e1bb6bSChristian Bruel struct phy_provider *phy_provider; 52547e1bb6bSChristian Bruel int ret, irq; 52647e1bb6bSChristian Bruel 52747e1bb6bSChristian Bruel combophy = devm_kzalloc(dev, sizeof(*combophy), GFP_KERNEL); 52847e1bb6bSChristian Bruel if (!combophy) 52947e1bb6bSChristian Bruel return -ENOMEM; 53047e1bb6bSChristian Bruel 53147e1bb6bSChristian Bruel combophy->dev = dev; 53247e1bb6bSChristian Bruel 53347e1bb6bSChristian Bruel dev_set_drvdata(dev, combophy); 53447e1bb6bSChristian Bruel 53547e1bb6bSChristian Bruel combophy->base = devm_platform_ioremap_resource(pdev, 0); 53647e1bb6bSChristian Bruel if (IS_ERR(combophy->base)) 53747e1bb6bSChristian Bruel return PTR_ERR(combophy->base); 53847e1bb6bSChristian Bruel 53947e1bb6bSChristian Bruel ret = stm32_combophy_get_clocks(combophy); 54047e1bb6bSChristian Bruel if (ret) 54147e1bb6bSChristian Bruel return ret; 54247e1bb6bSChristian Bruel 54347e1bb6bSChristian Bruel combophy->phy_reset = devm_reset_control_get_exclusive(dev, "phy"); 54447e1bb6bSChristian Bruel if (IS_ERR(combophy->phy_reset)) 54547e1bb6bSChristian Bruel return dev_err_probe(dev, PTR_ERR(combophy->phy_reset), 54647e1bb6bSChristian Bruel "Failed to get PHY reset\n"); 54747e1bb6bSChristian Bruel 54847e1bb6bSChristian Bruel combophy->regmap = syscon_regmap_lookup_by_compatible("st,stm32mp25-syscfg"); 54947e1bb6bSChristian Bruel if (IS_ERR(combophy->regmap)) 55047e1bb6bSChristian Bruel return dev_err_probe(dev, PTR_ERR(combophy->regmap), 55147e1bb6bSChristian Bruel "No syscfg specified\n"); 55247e1bb6bSChristian Bruel 55347e1bb6bSChristian Bruel combophy->phy = devm_phy_create(dev, NULL, &stm32_combophy_phy_data); 55447e1bb6bSChristian Bruel if (IS_ERR(combophy->phy)) 55547e1bb6bSChristian Bruel return dev_err_probe(dev, PTR_ERR(combophy->phy), 55647e1bb6bSChristian Bruel "failed to create PCIe/USB3 ComboPHY\n"); 55747e1bb6bSChristian Bruel 55847e1bb6bSChristian Bruel if (device_property_read_bool(dev, "wakeup-source")) { 55947e1bb6bSChristian Bruel irq = platform_get_irq(pdev, 0); 56047e1bb6bSChristian Bruel if (irq < 0) 56147e1bb6bSChristian Bruel return dev_err_probe(dev, irq, "failed to get IRQ\n"); 56247e1bb6bSChristian Bruel combophy->irq_wakeup = irq; 56347e1bb6bSChristian Bruel 56447e1bb6bSChristian Bruel ret = devm_request_threaded_irq(dev, combophy->irq_wakeup, NULL, 56547e1bb6bSChristian Bruel stm32_combophy_irq_wakeup_handler, IRQF_ONESHOT, 56647e1bb6bSChristian Bruel NULL, NULL); 56747e1bb6bSChristian Bruel if (ret) 56847e1bb6bSChristian Bruel return dev_err_probe(dev, ret, "unable to request wake IRQ %d\n", 56947e1bb6bSChristian Bruel combophy->irq_wakeup); 57047e1bb6bSChristian Bruel } 57147e1bb6bSChristian Bruel 57247e1bb6bSChristian Bruel ret = devm_pm_runtime_enable(dev); 57347e1bb6bSChristian Bruel if (ret) 57447e1bb6bSChristian Bruel return dev_err_probe(dev, ret, "Failed to enable pm runtime\n"); 57547e1bb6bSChristian Bruel 57647e1bb6bSChristian Bruel phy_set_drvdata(combophy->phy, combophy); 57747e1bb6bSChristian Bruel 57847e1bb6bSChristian Bruel phy_provider = devm_of_phy_provider_register(dev, stm32_combophy_xlate); 57947e1bb6bSChristian Bruel 58047e1bb6bSChristian Bruel return PTR_ERR_OR_ZERO(phy_provider); 58147e1bb6bSChristian Bruel } 58247e1bb6bSChristian Bruel 58347e1bb6bSChristian Bruel static const struct dev_pm_ops stm32_combophy_pm_ops = { 58447e1bb6bSChristian Bruel NOIRQ_SYSTEM_SLEEP_PM_OPS(stm32_combophy_suspend_noirq, 58547e1bb6bSChristian Bruel stm32_combophy_resume_noirq) 58647e1bb6bSChristian Bruel }; 58747e1bb6bSChristian Bruel 58847e1bb6bSChristian Bruel static const struct of_device_id stm32_combophy_of_match[] = { 58947e1bb6bSChristian Bruel { .compatible = "st,stm32mp25-combophy", }, 59047e1bb6bSChristian Bruel { }, 59147e1bb6bSChristian Bruel }; 59247e1bb6bSChristian Bruel MODULE_DEVICE_TABLE(of, stm32_combophy_of_match); 59347e1bb6bSChristian Bruel 59447e1bb6bSChristian Bruel static struct platform_driver stm32_combophy_driver = { 59547e1bb6bSChristian Bruel .probe = stm32_combophy_probe, 59647e1bb6bSChristian Bruel .driver = { 59747e1bb6bSChristian Bruel .name = "stm32-combophy", 59847e1bb6bSChristian Bruel .of_match_table = stm32_combophy_of_match, 59947e1bb6bSChristian Bruel .pm = pm_sleep_ptr(&stm32_combophy_pm_ops) 60047e1bb6bSChristian Bruel } 60147e1bb6bSChristian Bruel }; 60247e1bb6bSChristian Bruel 60347e1bb6bSChristian Bruel module_platform_driver(stm32_combophy_driver); 60447e1bb6bSChristian Bruel 60547e1bb6bSChristian Bruel MODULE_AUTHOR("Christian Bruel <christian.bruel@foss.st.com>"); 60647e1bb6bSChristian Bruel MODULE_DESCRIPTION("STM32MP25 Combophy USB3/PCIe controller driver"); 60747e1bb6bSChristian Bruel MODULE_LICENSE("GPL"); 608