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; 12547e1bb6bSChristian Bruel 12647e1bb6bSChristian Bruel if (!of_property_read_u32(combophy->dev->of_node, "st,output-micro-ohms", &val)) { 12747e1bb6bSChristian Bruel if (val < min_imp || val > max_imp) { 12847e1bb6bSChristian Bruel dev_err(combophy->dev, "Invalid value %u for output ohm\n", val); 12947e1bb6bSChristian Bruel return -EINVAL; 13047e1bb6bSChristian Bruel } 13147e1bb6bSChristian Bruel 13247e1bb6bSChristian Bruel for (imp_of = 0; imp_of < ARRAY_SIZE(imp_lookup); imp_of++) 13347e1bb6bSChristian Bruel if (imp_lookup[imp_of].microohm <= val) 13447e1bb6bSChristian Bruel break; 13547e1bb6bSChristian Bruel 13647e1bb6bSChristian Bruel dev_dbg(combophy->dev, "Set %u micro-ohms output impedance\n", 13747e1bb6bSChristian Bruel imp_lookup[imp_of].microohm); 13847e1bb6bSChristian Bruel 13947e1bb6bSChristian Bruel regmap_update_bits(combophy->regmap, SYSCFG_PCIEPRGCR, 14047e1bb6bSChristian Bruel STM32MP25_PCIEPRG_IMPCTRL_OHM, 14147e1bb6bSChristian Bruel FIELD_PREP(STM32MP25_PCIEPRG_IMPCTRL_OHM, imp_of)); 14247e1bb6bSChristian Bruel } else { 14347e1bb6bSChristian Bruel regmap_read(combophy->regmap, SYSCFG_PCIEPRGCR, &val); 14447e1bb6bSChristian Bruel imp_of = FIELD_GET(STM32MP25_PCIEPRG_IMPCTRL_OHM, val); 14547e1bb6bSChristian Bruel } 14647e1bb6bSChristian Bruel 14747e1bb6bSChristian Bruel if (!of_property_read_u32(combophy->dev->of_node, "st,output-vswing-microvolt", &val)) { 14847e1bb6bSChristian Bruel if (val < min_vswing || val > max_vswing) { 14947e1bb6bSChristian Bruel dev_err(combophy->dev, "Invalid value %u for output vswing\n", val); 15047e1bb6bSChristian Bruel return -EINVAL; 15147e1bb6bSChristian Bruel } 15247e1bb6bSChristian Bruel 15347e1bb6bSChristian Bruel for (vswing_of = 0; vswing_of < ARRAY_SIZE(imp_lookup[imp_of].vswing); vswing_of++) 15447e1bb6bSChristian Bruel if (imp_lookup[imp_of].vswing[vswing_of] >= val) 15547e1bb6bSChristian Bruel break; 15647e1bb6bSChristian Bruel 15747e1bb6bSChristian Bruel dev_dbg(combophy->dev, "Set %u microvolt swing\n", 15847e1bb6bSChristian Bruel imp_lookup[imp_of].vswing[vswing_of]); 15947e1bb6bSChristian Bruel 16047e1bb6bSChristian Bruel regmap_update_bits(combophy->regmap, SYSCFG_PCIEPRGCR, 16147e1bb6bSChristian Bruel STM32MP25_PCIEPRG_IMPCTRL_VSWING, 16247e1bb6bSChristian Bruel FIELD_PREP(STM32MP25_PCIEPRG_IMPCTRL_VSWING, vswing_of)); 16347e1bb6bSChristian Bruel } 16447e1bb6bSChristian Bruel 16547e1bb6bSChristian Bruel return 0; 16647e1bb6bSChristian Bruel } 16747e1bb6bSChristian Bruel 16847e1bb6bSChristian Bruel static int stm32_combophy_pll_init(struct stm32_combophy *combophy) 16947e1bb6bSChristian Bruel { 17047e1bb6bSChristian Bruel int ret; 17147e1bb6bSChristian Bruel u32 refclksel, pllmult, propcntrl, val; 17247e1bb6bSChristian Bruel u32 clk_rate; 17347e1bb6bSChristian Bruel struct clk *clk; 17447e1bb6bSChristian Bruel u32 cr1_val = 0, cr1_mask = 0; 17547e1bb6bSChristian Bruel 17647e1bb6bSChristian Bruel if (combophy->have_pad_clk) 17747e1bb6bSChristian Bruel clk = combophy->clks[PAD_CLK].clk; 17847e1bb6bSChristian Bruel else 17947e1bb6bSChristian Bruel clk = combophy->clks[KER_CLK].clk; 18047e1bb6bSChristian Bruel 18147e1bb6bSChristian Bruel clk_rate = clk_get_rate(clk); 18247e1bb6bSChristian Bruel 18347e1bb6bSChristian Bruel dev_dbg(combophy->dev, "%s pll init rate %d\n", 18447e1bb6bSChristian Bruel combophy->have_pad_clk ? "External" : "Ker", clk_rate); 18547e1bb6bSChristian Bruel 18647e1bb6bSChristian Bruel if (combophy->type != PHY_TYPE_PCIE) { 18747e1bb6bSChristian Bruel cr1_mask |= SYSCFG_COMBOPHY_CR1_REFSSPEN; 18847e1bb6bSChristian Bruel cr1_val |= SYSCFG_COMBOPHY_CR1_REFSSPEN; 18947e1bb6bSChristian Bruel } 19047e1bb6bSChristian Bruel 19147e1bb6bSChristian Bruel if (of_property_present(combophy->dev->of_node, "st,ssc-on")) { 19247e1bb6bSChristian Bruel dev_dbg(combophy->dev, "Enabling clock with SSC\n"); 19347e1bb6bSChristian Bruel cr1_mask |= SYSCFG_COMBOPHY_CR1_SSCEN; 19447e1bb6bSChristian Bruel cr1_val |= SYSCFG_COMBOPHY_CR1_SSCEN; 19547e1bb6bSChristian Bruel } 19647e1bb6bSChristian Bruel 19747e1bb6bSChristian Bruel switch (clk_rate) { 19847e1bb6bSChristian Bruel case 100000000: 19947e1bb6bSChristian Bruel pllmult = MPLLMULT_100; 20047e1bb6bSChristian Bruel refclksel = REFCLKSEL_0; 20147e1bb6bSChristian Bruel propcntrl = 0x8u << 4; 20247e1bb6bSChristian Bruel break; 20347e1bb6bSChristian Bruel case 19200000: 20447e1bb6bSChristian Bruel pllmult = MPLLMULT_19_2; 20547e1bb6bSChristian Bruel refclksel = REFCLKSEL_1; 20647e1bb6bSChristian Bruel propcntrl = 0x8u << 4; 20747e1bb6bSChristian Bruel break; 20847e1bb6bSChristian Bruel case 25000000: 20947e1bb6bSChristian Bruel pllmult = MPLLMULT_25; 21047e1bb6bSChristian Bruel refclksel = REFCLKSEL_0; 21147e1bb6bSChristian Bruel propcntrl = 0xeu << 4; 21247e1bb6bSChristian Bruel break; 21347e1bb6bSChristian Bruel case 24000000: 21447e1bb6bSChristian Bruel pllmult = MPLLMULT_24; 21547e1bb6bSChristian Bruel refclksel = REFCLKSEL_1; 21647e1bb6bSChristian Bruel propcntrl = 0xeu << 4; 21747e1bb6bSChristian Bruel break; 21847e1bb6bSChristian Bruel case 20000000: 21947e1bb6bSChristian Bruel pllmult = MPLLMULT_20; 22047e1bb6bSChristian Bruel refclksel = REFCLKSEL_0; 22147e1bb6bSChristian Bruel propcntrl = 0xeu << 4; 22247e1bb6bSChristian Bruel break; 22347e1bb6bSChristian Bruel default: 22447e1bb6bSChristian Bruel dev_err(combophy->dev, "Invalid rate 0x%x\n", clk_rate); 22547e1bb6bSChristian Bruel return -EINVAL; 226*e592a655SYang Li } 22747e1bb6bSChristian Bruel 22847e1bb6bSChristian Bruel cr1_mask |= SYSCFG_COMBOPHY_CR1_REFCLKDIV2; 22947e1bb6bSChristian Bruel cr1_val |= REFCLDIV_0; 23047e1bb6bSChristian Bruel 23147e1bb6bSChristian Bruel cr1_mask |= SYSCFG_COMBOPHY_CR1_REFCLKSEL; 23247e1bb6bSChristian Bruel cr1_val |= refclksel; 23347e1bb6bSChristian Bruel 23447e1bb6bSChristian Bruel cr1_mask |= SYSCFG_COMBOPHY_CR1_MPLLMULT; 23547e1bb6bSChristian Bruel cr1_val |= pllmult; 23647e1bb6bSChristian Bruel 23747e1bb6bSChristian Bruel /* 23847e1bb6bSChristian Bruel * vddcombophy is interconnected with vddcore. Isolation bit should be unset 23947e1bb6bSChristian Bruel * before using the ComboPHY. 24047e1bb6bSChristian Bruel */ 24147e1bb6bSChristian Bruel regmap_update_bits(combophy->regmap, SYSCFG_COMBOPHY_CR2, 24247e1bb6bSChristian Bruel SYSCFG_COMBOPHY_CR2_ISO_DIS, SYSCFG_COMBOPHY_CR2_ISO_DIS); 24347e1bb6bSChristian Bruel 24447e1bb6bSChristian Bruel reset_control_assert(combophy->phy_reset); 24547e1bb6bSChristian Bruel 24647e1bb6bSChristian Bruel if (combophy->type == PHY_TYPE_PCIE) { 24747e1bb6bSChristian Bruel ret = stm32_impedance_tune(combophy); 24847e1bb6bSChristian Bruel if (ret) 24947e1bb6bSChristian Bruel goto out_iso; 25047e1bb6bSChristian Bruel 25147e1bb6bSChristian Bruel cr1_mask |= SYSCFG_COMBOPHY_CR1_REFUSEPAD; 25247e1bb6bSChristian Bruel cr1_val |= combophy->have_pad_clk ? SYSCFG_COMBOPHY_CR1_REFUSEPAD : 0; 25347e1bb6bSChristian Bruel } 25447e1bb6bSChristian Bruel 25547e1bb6bSChristian Bruel if (!of_property_read_u32(combophy->dev->of_node, "st,rx-equalizer", &val)) { 25647e1bb6bSChristian Bruel dev_dbg(combophy->dev, "Set RX equalizer %u\n", val); 25747e1bb6bSChristian Bruel if (val > SYSCFG_COMBOPHY_CR4_RX0_EQ) { 25847e1bb6bSChristian Bruel dev_err(combophy->dev, "Invalid value %u for rx0 equalizer\n", val); 25947e1bb6bSChristian Bruel ret = -EINVAL; 26047e1bb6bSChristian Bruel goto out_iso; 26147e1bb6bSChristian Bruel } 26247e1bb6bSChristian Bruel 26347e1bb6bSChristian Bruel regmap_update_bits(combophy->regmap, SYSCFG_COMBOPHY_CR4, 26447e1bb6bSChristian Bruel SYSCFG_COMBOPHY_CR4_RX0_EQ, val); 26547e1bb6bSChristian Bruel } 26647e1bb6bSChristian Bruel 26747e1bb6bSChristian Bruel regmap_update_bits(combophy->regmap, SYSCFG_COMBOPHY_CR1, cr1_mask, cr1_val); 26847e1bb6bSChristian Bruel 26947e1bb6bSChristian Bruel /* 27047e1bb6bSChristian Bruel * Force elasticity buffer to be tuned for the reference clock as 27147e1bb6bSChristian Bruel * the separated clock model is not supported 27247e1bb6bSChristian Bruel */ 27347e1bb6bSChristian Bruel regmap_update_bits(combophy->regmap, SYSCFG_COMBOPHY_CR5, 27447e1bb6bSChristian Bruel SYSCFG_COMBOPHY_CR5_COMMON_CLOCKS, SYSCFG_COMBOPHY_CR5_COMMON_CLOCKS); 27547e1bb6bSChristian Bruel 27647e1bb6bSChristian Bruel reset_control_deassert(combophy->phy_reset); 27747e1bb6bSChristian Bruel 27847e1bb6bSChristian Bruel ret = regmap_read_poll_timeout(combophy->regmap, SYSCFG_COMBOPHY_SR, val, 27947e1bb6bSChristian Bruel !(val & STM32MP25_PIPE0_PHYSTATUS), 28047e1bb6bSChristian Bruel 10, 1000); 28147e1bb6bSChristian Bruel if (ret) { 28247e1bb6bSChristian Bruel dev_err(combophy->dev, "timeout, cannot lock PLL\n"); 28347e1bb6bSChristian Bruel if (combophy->type == PHY_TYPE_PCIE && !combophy->have_pad_clk) 28447e1bb6bSChristian Bruel regmap_update_bits(combophy->regmap, SYSCFG_PCIEPRGCR, 28547e1bb6bSChristian Bruel STM32MP25_PCIEPRGCR_EN, 0); 28647e1bb6bSChristian Bruel 28747e1bb6bSChristian Bruel if (combophy->type != PHY_TYPE_PCIE) 28847e1bb6bSChristian Bruel regmap_update_bits(combophy->regmap, SYSCFG_COMBOPHY_CR1, 28947e1bb6bSChristian Bruel SYSCFG_COMBOPHY_CR1_REFSSPEN, 0); 29047e1bb6bSChristian Bruel 29147e1bb6bSChristian Bruel goto out; 29247e1bb6bSChristian Bruel } 29347e1bb6bSChristian Bruel 29447e1bb6bSChristian Bruel 29547e1bb6bSChristian Bruel if (combophy->type == PHY_TYPE_PCIE) { 29647e1bb6bSChristian Bruel if (!combophy->have_pad_clk) 29747e1bb6bSChristian Bruel regmap_update_bits(combophy->regmap, SYSCFG_PCIEPRGCR, 29847e1bb6bSChristian Bruel STM32MP25_PCIEPRGCR_EN, STM32MP25_PCIEPRGCR_EN); 29947e1bb6bSChristian Bruel 30047e1bb6bSChristian Bruel val = readl_relaxed(combophy->base + COMBOPHY_SUP_ANA_MPLL_LOOP_CTL); 30147e1bb6bSChristian Bruel val &= ~COMBOPHY_PROP_CNTRL; 30247e1bb6bSChristian Bruel val |= propcntrl; 30347e1bb6bSChristian Bruel writel_relaxed(val, combophy->base + COMBOPHY_SUP_ANA_MPLL_LOOP_CTL); 30447e1bb6bSChristian Bruel } 30547e1bb6bSChristian Bruel 30647e1bb6bSChristian Bruel return 0; 30747e1bb6bSChristian Bruel 30847e1bb6bSChristian Bruel out_iso: 30947e1bb6bSChristian Bruel reset_control_deassert(combophy->phy_reset); 31047e1bb6bSChristian Bruel 31147e1bb6bSChristian Bruel out: 31247e1bb6bSChristian Bruel regmap_update_bits(combophy->regmap, SYSCFG_COMBOPHY_CR2, 31347e1bb6bSChristian Bruel SYSCFG_COMBOPHY_CR2_ISO_DIS, 0); 31447e1bb6bSChristian Bruel 31547e1bb6bSChristian Bruel return ret; 31647e1bb6bSChristian Bruel } 31747e1bb6bSChristian Bruel 31847e1bb6bSChristian Bruel static struct phy *stm32_combophy_xlate(struct device *dev, 31947e1bb6bSChristian Bruel const struct of_phandle_args *args) 32047e1bb6bSChristian Bruel { 32147e1bb6bSChristian Bruel struct stm32_combophy *combophy = dev_get_drvdata(dev); 32247e1bb6bSChristian Bruel unsigned int type; 32347e1bb6bSChristian Bruel 32447e1bb6bSChristian Bruel if (args->args_count != 1) { 32547e1bb6bSChristian Bruel dev_err(dev, "invalid number of cells in 'phy' property\n"); 32647e1bb6bSChristian Bruel return ERR_PTR(-EINVAL); 32747e1bb6bSChristian Bruel } 32847e1bb6bSChristian Bruel 32947e1bb6bSChristian Bruel type = args->args[0]; 33047e1bb6bSChristian Bruel if (type != PHY_TYPE_USB3 && type != PHY_TYPE_PCIE) { 33147e1bb6bSChristian Bruel dev_err(dev, "unsupported device type: %d\n", type); 33247e1bb6bSChristian Bruel return ERR_PTR(-EINVAL); 33347e1bb6bSChristian Bruel } 33447e1bb6bSChristian Bruel 33547e1bb6bSChristian Bruel if (combophy->have_pad_clk && type != PHY_TYPE_PCIE) { 33647e1bb6bSChristian Bruel dev_err(dev, "Invalid use of clk_pad for USB3 mode\n"); 33747e1bb6bSChristian Bruel return ERR_PTR(-EINVAL); 33847e1bb6bSChristian Bruel } 33947e1bb6bSChristian Bruel 34047e1bb6bSChristian Bruel combophy->type = type; 34147e1bb6bSChristian Bruel 34247e1bb6bSChristian Bruel return combophy->phy; 34347e1bb6bSChristian Bruel } 34447e1bb6bSChristian Bruel 34547e1bb6bSChristian Bruel static int stm32_combophy_set_mode(struct stm32_combophy *combophy) 34647e1bb6bSChristian Bruel { 34747e1bb6bSChristian Bruel int type = combophy->type; 34847e1bb6bSChristian Bruel u32 val; 34947e1bb6bSChristian Bruel 35047e1bb6bSChristian Bruel switch (type) { 35147e1bb6bSChristian Bruel case PHY_TYPE_PCIE: 35247e1bb6bSChristian Bruel dev_dbg(combophy->dev, "setting PCIe ComboPHY\n"); 35347e1bb6bSChristian Bruel val = COMBOPHY_MODESEL_PCIE; 35447e1bb6bSChristian Bruel break; 35547e1bb6bSChristian Bruel case PHY_TYPE_USB3: 35647e1bb6bSChristian Bruel dev_dbg(combophy->dev, "setting USB3 ComboPHY\n"); 35747e1bb6bSChristian Bruel val = COMBOPHY_MODESEL_USB; 35847e1bb6bSChristian Bruel break; 35947e1bb6bSChristian Bruel default: 36047e1bb6bSChristian Bruel dev_err(combophy->dev, "Invalid PHY mode %d\n", type); 36147e1bb6bSChristian Bruel return -EINVAL; 36247e1bb6bSChristian Bruel } 36347e1bb6bSChristian Bruel 36447e1bb6bSChristian Bruel return regmap_update_bits(combophy->regmap, SYSCFG_COMBOPHY_CR2, 36547e1bb6bSChristian Bruel SYSCFG_COMBOPHY_CR2_MODESEL, val); 36647e1bb6bSChristian Bruel } 36747e1bb6bSChristian Bruel 36847e1bb6bSChristian Bruel static int stm32_combophy_suspend_noirq(struct device *dev) 36947e1bb6bSChristian Bruel { 37047e1bb6bSChristian Bruel struct stm32_combophy *combophy = dev_get_drvdata(dev); 37147e1bb6bSChristian Bruel 37247e1bb6bSChristian Bruel /* 37347e1bb6bSChristian Bruel * Clocks should be turned off since it is not needed for 37447e1bb6bSChristian Bruel * wakeup capability. In case usb-remote wakeup is not enabled, 37547e1bb6bSChristian Bruel * combo-phy is already turned off by HCD driver using exit callback 37647e1bb6bSChristian Bruel */ 37747e1bb6bSChristian Bruel if (combophy->is_init) { 37847e1bb6bSChristian Bruel clk_bulk_disable_unprepare(combophy->num_clks, combophy->clks); 37947e1bb6bSChristian Bruel 38047e1bb6bSChristian Bruel /* since wakeup is enabled for ctrl */ 38147e1bb6bSChristian Bruel enable_irq_wake(combophy->irq_wakeup); 38247e1bb6bSChristian Bruel } 38347e1bb6bSChristian Bruel 38447e1bb6bSChristian Bruel return 0; 38547e1bb6bSChristian Bruel } 38647e1bb6bSChristian Bruel 38747e1bb6bSChristian Bruel static int stm32_combophy_resume_noirq(struct device *dev) 38847e1bb6bSChristian Bruel { 38947e1bb6bSChristian Bruel struct stm32_combophy *combophy = dev_get_drvdata(dev); 39047e1bb6bSChristian Bruel int ret; 39147e1bb6bSChristian Bruel 39247e1bb6bSChristian Bruel /* 39347e1bb6bSChristian Bruel * If clocks was turned off by suspend call for wakeup then needs 39447e1bb6bSChristian Bruel * to be turned back ON in resume. In case usb-remote wakeup is not 39547e1bb6bSChristian Bruel * enabled, clocks already turned ON by HCD driver using init callback 39647e1bb6bSChristian Bruel */ 39747e1bb6bSChristian Bruel if (combophy->is_init) { 39847e1bb6bSChristian Bruel /* since wakeup was enabled for ctrl */ 39947e1bb6bSChristian Bruel disable_irq_wake(combophy->irq_wakeup); 40047e1bb6bSChristian Bruel 40147e1bb6bSChristian Bruel ret = clk_bulk_prepare_enable(combophy->num_clks, combophy->clks); 40247e1bb6bSChristian Bruel if (ret) { 40347e1bb6bSChristian Bruel dev_err(dev, "can't enable clocks (%d)\n", ret); 40447e1bb6bSChristian Bruel return ret; 40547e1bb6bSChristian Bruel } 40647e1bb6bSChristian Bruel } 40747e1bb6bSChristian Bruel 40847e1bb6bSChristian Bruel return 0; 40947e1bb6bSChristian Bruel } 41047e1bb6bSChristian Bruel 41147e1bb6bSChristian Bruel static int stm32_combophy_exit(struct phy *phy) 41247e1bb6bSChristian Bruel { 41347e1bb6bSChristian Bruel struct stm32_combophy *combophy = phy_get_drvdata(phy); 41447e1bb6bSChristian Bruel struct device *dev = combophy->dev; 41547e1bb6bSChristian Bruel 41647e1bb6bSChristian Bruel combophy->is_init = false; 41747e1bb6bSChristian Bruel 41847e1bb6bSChristian Bruel if (combophy->type == PHY_TYPE_PCIE && !combophy->have_pad_clk) 41947e1bb6bSChristian Bruel regmap_update_bits(combophy->regmap, SYSCFG_PCIEPRGCR, 42047e1bb6bSChristian Bruel STM32MP25_PCIEPRGCR_EN, 0); 42147e1bb6bSChristian Bruel 42247e1bb6bSChristian Bruel if (combophy->type != PHY_TYPE_PCIE) 42347e1bb6bSChristian Bruel regmap_update_bits(combophy->regmap, SYSCFG_COMBOPHY_CR1, 42447e1bb6bSChristian Bruel SYSCFG_COMBOPHY_CR1_REFSSPEN, 0); 42547e1bb6bSChristian Bruel 42647e1bb6bSChristian Bruel regmap_update_bits(combophy->regmap, SYSCFG_COMBOPHY_CR2, 42747e1bb6bSChristian Bruel SYSCFG_COMBOPHY_CR2_ISO_DIS, 0); 42847e1bb6bSChristian Bruel 42947e1bb6bSChristian Bruel clk_bulk_disable_unprepare(combophy->num_clks, combophy->clks); 43047e1bb6bSChristian Bruel 43147e1bb6bSChristian Bruel pm_runtime_put_noidle(dev); 43247e1bb6bSChristian Bruel 43347e1bb6bSChristian Bruel return 0; 43447e1bb6bSChristian Bruel } 43547e1bb6bSChristian Bruel 43647e1bb6bSChristian Bruel static int stm32_combophy_init(struct phy *phy) 43747e1bb6bSChristian Bruel { 43847e1bb6bSChristian Bruel struct stm32_combophy *combophy = phy_get_drvdata(phy); 43947e1bb6bSChristian Bruel struct device *dev = combophy->dev; 44047e1bb6bSChristian Bruel int ret; 44147e1bb6bSChristian Bruel 44247e1bb6bSChristian Bruel pm_runtime_get_noresume(dev); 44347e1bb6bSChristian Bruel 44447e1bb6bSChristian Bruel ret = clk_bulk_prepare_enable(combophy->num_clks, combophy->clks); 44547e1bb6bSChristian Bruel if (ret) { 44647e1bb6bSChristian Bruel dev_err(dev, "can't enable clocks (%d)\n", ret); 44747e1bb6bSChristian Bruel pm_runtime_put_noidle(dev); 44847e1bb6bSChristian Bruel return ret; 44947e1bb6bSChristian Bruel } 45047e1bb6bSChristian Bruel 45147e1bb6bSChristian Bruel ret = stm32_combophy_set_mode(combophy); 45247e1bb6bSChristian Bruel if (ret) { 45347e1bb6bSChristian Bruel dev_err(dev, "combophy mode not set\n"); 45447e1bb6bSChristian Bruel clk_bulk_disable_unprepare(combophy->num_clks, combophy->clks); 45547e1bb6bSChristian Bruel pm_runtime_put_noidle(dev); 45647e1bb6bSChristian Bruel return ret; 45747e1bb6bSChristian Bruel } 45847e1bb6bSChristian Bruel 45947e1bb6bSChristian Bruel ret = stm32_combophy_pll_init(combophy); 46047e1bb6bSChristian Bruel if (ret) { 46147e1bb6bSChristian Bruel clk_bulk_disable_unprepare(combophy->num_clks, combophy->clks); 46247e1bb6bSChristian Bruel pm_runtime_put_noidle(dev); 46347e1bb6bSChristian Bruel return ret; 46447e1bb6bSChristian Bruel } 46547e1bb6bSChristian Bruel 46647e1bb6bSChristian Bruel pm_runtime_disable(dev); 46747e1bb6bSChristian Bruel pm_runtime_set_active(dev); 46847e1bb6bSChristian Bruel pm_runtime_enable(dev); 46947e1bb6bSChristian Bruel 47047e1bb6bSChristian Bruel combophy->is_init = true; 47147e1bb6bSChristian Bruel 47247e1bb6bSChristian Bruel return ret; 47347e1bb6bSChristian Bruel } 47447e1bb6bSChristian Bruel 47547e1bb6bSChristian Bruel static const struct phy_ops stm32_combophy_phy_data = { 47647e1bb6bSChristian Bruel .init = stm32_combophy_init, 47747e1bb6bSChristian Bruel .exit = stm32_combophy_exit, 47847e1bb6bSChristian Bruel .owner = THIS_MODULE 47947e1bb6bSChristian Bruel }; 48047e1bb6bSChristian Bruel 48147e1bb6bSChristian Bruel static irqreturn_t stm32_combophy_irq_wakeup_handler(int irq, void *dev_id) 48247e1bb6bSChristian Bruel { 48347e1bb6bSChristian Bruel return IRQ_HANDLED; 48447e1bb6bSChristian Bruel } 48547e1bb6bSChristian Bruel 48647e1bb6bSChristian Bruel static int stm32_combophy_get_clocks(struct stm32_combophy *combophy) 48747e1bb6bSChristian Bruel { 48847e1bb6bSChristian Bruel int i, ret; 48947e1bb6bSChristian Bruel 49047e1bb6bSChristian Bruel for (i = 0; i < ARRAY_SIZE(combophy_clks); i++) 49147e1bb6bSChristian Bruel combophy->clks[i].id = combophy_clks[i]; 49247e1bb6bSChristian Bruel 49347e1bb6bSChristian Bruel combophy->num_clks = ARRAY_SIZE(combophy_clks) - 1; 49447e1bb6bSChristian Bruel 49547e1bb6bSChristian Bruel ret = devm_clk_bulk_get(combophy->dev, combophy->num_clks, combophy->clks); 49647e1bb6bSChristian Bruel if (ret) 49747e1bb6bSChristian Bruel return ret; 49847e1bb6bSChristian Bruel 49947e1bb6bSChristian Bruel ret = devm_clk_bulk_get_optional(combophy->dev, 1, combophy->clks + combophy->num_clks); 50047e1bb6bSChristian Bruel if (ret) 50147e1bb6bSChristian Bruel return ret; 50247e1bb6bSChristian Bruel 50347e1bb6bSChristian Bruel if (combophy->clks[combophy->num_clks].clk != NULL) { 50447e1bb6bSChristian Bruel combophy->have_pad_clk = true; 50547e1bb6bSChristian Bruel combophy->num_clks++; 50647e1bb6bSChristian Bruel } 50747e1bb6bSChristian Bruel 50847e1bb6bSChristian Bruel return 0; 50947e1bb6bSChristian Bruel } 51047e1bb6bSChristian Bruel 51147e1bb6bSChristian Bruel static int stm32_combophy_probe(struct platform_device *pdev) 51247e1bb6bSChristian Bruel { 51347e1bb6bSChristian Bruel struct stm32_combophy *combophy; 51447e1bb6bSChristian Bruel struct device *dev = &pdev->dev; 51547e1bb6bSChristian Bruel struct phy_provider *phy_provider; 51647e1bb6bSChristian Bruel int ret, irq; 51747e1bb6bSChristian Bruel 51847e1bb6bSChristian Bruel combophy = devm_kzalloc(dev, sizeof(*combophy), GFP_KERNEL); 51947e1bb6bSChristian Bruel if (!combophy) 52047e1bb6bSChristian Bruel return -ENOMEM; 52147e1bb6bSChristian Bruel 52247e1bb6bSChristian Bruel combophy->dev = dev; 52347e1bb6bSChristian Bruel 52447e1bb6bSChristian Bruel dev_set_drvdata(dev, combophy); 52547e1bb6bSChristian Bruel 52647e1bb6bSChristian Bruel combophy->base = devm_platform_ioremap_resource(pdev, 0); 52747e1bb6bSChristian Bruel if (IS_ERR(combophy->base)) 52847e1bb6bSChristian Bruel return PTR_ERR(combophy->base); 52947e1bb6bSChristian Bruel 53047e1bb6bSChristian Bruel ret = stm32_combophy_get_clocks(combophy); 53147e1bb6bSChristian Bruel if (ret) 53247e1bb6bSChristian Bruel return ret; 53347e1bb6bSChristian Bruel 53447e1bb6bSChristian Bruel combophy->phy_reset = devm_reset_control_get_exclusive(dev, "phy"); 53547e1bb6bSChristian Bruel if (IS_ERR(combophy->phy_reset)) 53647e1bb6bSChristian Bruel return dev_err_probe(dev, PTR_ERR(combophy->phy_reset), 53747e1bb6bSChristian Bruel "Failed to get PHY reset\n"); 53847e1bb6bSChristian Bruel 53947e1bb6bSChristian Bruel combophy->regmap = syscon_regmap_lookup_by_compatible("st,stm32mp25-syscfg"); 54047e1bb6bSChristian Bruel if (IS_ERR(combophy->regmap)) 54147e1bb6bSChristian Bruel return dev_err_probe(dev, PTR_ERR(combophy->regmap), 54247e1bb6bSChristian Bruel "No syscfg specified\n"); 54347e1bb6bSChristian Bruel 54447e1bb6bSChristian Bruel combophy->phy = devm_phy_create(dev, NULL, &stm32_combophy_phy_data); 54547e1bb6bSChristian Bruel if (IS_ERR(combophy->phy)) 54647e1bb6bSChristian Bruel return dev_err_probe(dev, PTR_ERR(combophy->phy), 54747e1bb6bSChristian Bruel "failed to create PCIe/USB3 ComboPHY\n"); 54847e1bb6bSChristian Bruel 54947e1bb6bSChristian Bruel if (device_property_read_bool(dev, "wakeup-source")) { 55047e1bb6bSChristian Bruel irq = platform_get_irq(pdev, 0); 55147e1bb6bSChristian Bruel if (irq < 0) 55247e1bb6bSChristian Bruel return dev_err_probe(dev, irq, "failed to get IRQ\n"); 55347e1bb6bSChristian Bruel combophy->irq_wakeup = irq; 55447e1bb6bSChristian Bruel 55547e1bb6bSChristian Bruel ret = devm_request_threaded_irq(dev, combophy->irq_wakeup, NULL, 55647e1bb6bSChristian Bruel stm32_combophy_irq_wakeup_handler, IRQF_ONESHOT, 55747e1bb6bSChristian Bruel NULL, NULL); 55847e1bb6bSChristian Bruel if (ret) 55947e1bb6bSChristian Bruel return dev_err_probe(dev, ret, "unable to request wake IRQ %d\n", 56047e1bb6bSChristian Bruel combophy->irq_wakeup); 56147e1bb6bSChristian Bruel } 56247e1bb6bSChristian Bruel 56347e1bb6bSChristian Bruel ret = devm_pm_runtime_enable(dev); 56447e1bb6bSChristian Bruel if (ret) 56547e1bb6bSChristian Bruel return dev_err_probe(dev, ret, "Failed to enable pm runtime\n"); 56647e1bb6bSChristian Bruel 56747e1bb6bSChristian Bruel phy_set_drvdata(combophy->phy, combophy); 56847e1bb6bSChristian Bruel 56947e1bb6bSChristian Bruel phy_provider = devm_of_phy_provider_register(dev, stm32_combophy_xlate); 57047e1bb6bSChristian Bruel 57147e1bb6bSChristian Bruel return PTR_ERR_OR_ZERO(phy_provider); 57247e1bb6bSChristian Bruel } 57347e1bb6bSChristian Bruel 57447e1bb6bSChristian Bruel static const struct dev_pm_ops stm32_combophy_pm_ops = { 57547e1bb6bSChristian Bruel NOIRQ_SYSTEM_SLEEP_PM_OPS(stm32_combophy_suspend_noirq, 57647e1bb6bSChristian Bruel stm32_combophy_resume_noirq) 57747e1bb6bSChristian Bruel }; 57847e1bb6bSChristian Bruel 57947e1bb6bSChristian Bruel static const struct of_device_id stm32_combophy_of_match[] = { 58047e1bb6bSChristian Bruel { .compatible = "st,stm32mp25-combophy", }, 58147e1bb6bSChristian Bruel { }, 58247e1bb6bSChristian Bruel }; 58347e1bb6bSChristian Bruel MODULE_DEVICE_TABLE(of, stm32_combophy_of_match); 58447e1bb6bSChristian Bruel 58547e1bb6bSChristian Bruel static struct platform_driver stm32_combophy_driver = { 58647e1bb6bSChristian Bruel .probe = stm32_combophy_probe, 58747e1bb6bSChristian Bruel .driver = { 58847e1bb6bSChristian Bruel .name = "stm32-combophy", 58947e1bb6bSChristian Bruel .of_match_table = stm32_combophy_of_match, 59047e1bb6bSChristian Bruel .pm = pm_sleep_ptr(&stm32_combophy_pm_ops) 59147e1bb6bSChristian Bruel } 59247e1bb6bSChristian Bruel }; 59347e1bb6bSChristian Bruel 59447e1bb6bSChristian Bruel module_platform_driver(stm32_combophy_driver); 59547e1bb6bSChristian Bruel 59647e1bb6bSChristian Bruel MODULE_AUTHOR("Christian Bruel <christian.bruel@foss.st.com>"); 59747e1bb6bSChristian Bruel MODULE_DESCRIPTION("STM32MP25 Combophy USB3/PCIe controller driver"); 59847e1bb6bSChristian Bruel MODULE_LICENSE("GPL"); 599