// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2023, Linaro Limited */ #include #include #include #include #include #include /* eUSB2 status registers */ #define EUSB2_RPTR_STATUS 0x08 #define RPTR_OK BIT(7) /* eUSB2 control registers */ #define EUSB2_EN_CTL1 0x46 #define EUSB2_RPTR_EN BIT(7) #define EUSB2_FORCE_EN_5 0xe8 #define F_CLK_19P2M_EN BIT(6) #define EUSB2_FORCE_VAL_5 0xeD #define V_CLK_19P2M_EN BIT(6) #define EUSB2_TUNE_USB2_CROSSOVER 0x50 #define EUSB2_TUNE_IUSB2 0x51 #define EUSB2_TUNE_RES_FSDIF 0x52 #define EUSB2_TUNE_HSDISC 0x53 #define EUSB2_TUNE_SQUELCH_U 0x54 #define EUSB2_TUNE_USB2_SLEW 0x55 #define EUSB2_TUNE_USB2_EQU 0x56 #define EUSB2_TUNE_USB2_PREEM 0x57 #define EUSB2_TUNE_USB2_HS_COMP_CUR 0x58 #define EUSB2_TUNE_EUSB_SLEW 0x59 #define EUSB2_TUNE_EUSB_EQU 0x5A #define EUSB2_TUNE_EUSB_HS_COMP_CUR 0x5B enum eusb2_reg_layout { TUNE_EUSB_HS_COMP_CUR, TUNE_EUSB_EQU, TUNE_EUSB_SLEW, TUNE_USB2_HS_COMP_CUR, TUNE_USB2_PREEM, TUNE_USB2_EQU, TUNE_USB2_SLEW, TUNE_SQUELCH_U, TUNE_HSDISC, TUNE_RES_FSDIF, TUNE_IUSB2, TUNE_USB2_CROSSOVER, NUM_TUNE_FIELDS, FORCE_VAL_5 = NUM_TUNE_FIELDS, FORCE_EN_5, EN_CTL1, RPTR_STATUS, LAYOUT_SIZE, }; struct eusb2_repeater_cfg { const u32 *init_tbl; int init_tbl_num; const char * const *vreg_list; int num_vregs; }; struct eusb2_repeater { struct device *dev; struct regmap *regmap; struct phy *phy; struct regulator_bulk_data *vregs; const struct eusb2_repeater_cfg *cfg; u32 base; enum phy_mode mode; }; static const char * const pm8550b_vreg_l[] = { "vdd18", "vdd3", }; static const u32 pm8550b_init_tbl[NUM_TUNE_FIELDS] = { [TUNE_IUSB2] = 0x8, [TUNE_SQUELCH_U] = 0x3, [TUNE_USB2_PREEM] = 0x5, }; static const u32 smb2360_init_tbl[NUM_TUNE_FIELDS] = { [TUNE_IUSB2] = 0x5, [TUNE_SQUELCH_U] = 0x3, [TUNE_USB2_PREEM] = 0x2, }; static const struct eusb2_repeater_cfg pm8550b_eusb2_cfg = { .init_tbl = pm8550b_init_tbl, .init_tbl_num = ARRAY_SIZE(pm8550b_init_tbl), .vreg_list = pm8550b_vreg_l, .num_vregs = ARRAY_SIZE(pm8550b_vreg_l), }; static const struct eusb2_repeater_cfg smb2360_eusb2_cfg = { .init_tbl = smb2360_init_tbl, .init_tbl_num = ARRAY_SIZE(smb2360_init_tbl), .vreg_list = pm8550b_vreg_l, .num_vregs = ARRAY_SIZE(pm8550b_vreg_l), }; static int eusb2_repeater_init_vregs(struct eusb2_repeater *rptr) { int num = rptr->cfg->num_vregs; struct device *dev = rptr->dev; int i; rptr->vregs = devm_kcalloc(dev, num, sizeof(*rptr->vregs), GFP_KERNEL); if (!rptr->vregs) return -ENOMEM; for (i = 0; i < num; i++) rptr->vregs[i].supply = rptr->cfg->vreg_list[i]; return devm_regulator_bulk_get(dev, num, rptr->vregs); } static int eusb2_repeater_init(struct phy *phy) { struct eusb2_repeater *rptr = phy_get_drvdata(phy); struct device_node *np = rptr->dev->of_node; struct regmap *regmap = rptr->regmap; const u32 *init_tbl = rptr->cfg->init_tbl; u8 tune_usb2_preem = init_tbl[TUNE_USB2_PREEM]; u8 tune_hsdisc = init_tbl[TUNE_HSDISC]; u8 tune_iusb2 = init_tbl[TUNE_IUSB2]; u32 base = rptr->base; u32 val; int ret; of_property_read_u8(np, "qcom,tune-usb2-amplitude", &tune_iusb2); of_property_read_u8(np, "qcom,tune-usb2-disc-thres", &tune_hsdisc); of_property_read_u8(np, "qcom,tune-usb2-preem", &tune_usb2_preem); ret = regulator_bulk_enable(rptr->cfg->num_vregs, rptr->vregs); if (ret) return ret; regmap_write(regmap, base + EUSB2_EN_CTL1, EUSB2_RPTR_EN); regmap_write(regmap, base + EUSB2_TUNE_EUSB_HS_COMP_CUR, init_tbl[TUNE_EUSB_HS_COMP_CUR]); regmap_write(regmap, base + EUSB2_TUNE_EUSB_EQU, init_tbl[TUNE_EUSB_EQU]); regmap_write(regmap, base + EUSB2_TUNE_EUSB_SLEW, init_tbl[TUNE_EUSB_SLEW]); regmap_write(regmap, base + EUSB2_TUNE_USB2_HS_COMP_CUR, init_tbl[TUNE_USB2_HS_COMP_CUR]); regmap_write(regmap, base + EUSB2_TUNE_USB2_EQU, init_tbl[TUNE_USB2_EQU]); regmap_write(regmap, base + EUSB2_TUNE_USB2_SLEW, init_tbl[TUNE_USB2_SLEW]); regmap_write(regmap, base + EUSB2_TUNE_SQUELCH_U, init_tbl[TUNE_SQUELCH_U]); regmap_write(regmap, base + EUSB2_TUNE_RES_FSDIF, init_tbl[TUNE_RES_FSDIF]); regmap_write(regmap, base + EUSB2_TUNE_USB2_CROSSOVER, init_tbl[TUNE_USB2_CROSSOVER]); regmap_write(regmap, base + EUSB2_TUNE_USB2_PREEM, tune_usb2_preem); regmap_write(regmap, base + EUSB2_TUNE_HSDISC, tune_hsdisc); regmap_write(regmap, base + EUSB2_TUNE_IUSB2, tune_iusb2); ret = regmap_read_poll_timeout(regmap, base + EUSB2_RPTR_STATUS, val, val & RPTR_OK, 10, 5); if (ret) dev_err(rptr->dev, "initialization timed-out\n"); return ret; } static int eusb2_repeater_set_mode(struct phy *phy, enum phy_mode mode, int submode) { struct eusb2_repeater *rptr = phy_get_drvdata(phy); struct regmap *regmap = rptr->regmap; u32 base = rptr->base; switch (mode) { case PHY_MODE_USB_HOST: /* * CM.Lx is prohibited when repeater is already into Lx state as * per eUSB 1.2 Spec. Below implement software workaround until * PHY and controller is fixing seen observation. */ regmap_write(regmap, base + EUSB2_FORCE_EN_5, F_CLK_19P2M_EN); regmap_write(regmap, base + EUSB2_FORCE_VAL_5, V_CLK_19P2M_EN); break; case PHY_MODE_USB_DEVICE: /* * In device mode clear host mode related workaround as there * is no repeater reset available, and enable/disable of * repeater doesn't clear previous value due to shared * regulators (say host <-> device mode switch). */ regmap_write(regmap, base + EUSB2_FORCE_EN_5, 0); regmap_write(regmap, base + EUSB2_FORCE_VAL_5, 0); break; default: return -EINVAL; } return 0; } static int eusb2_repeater_exit(struct phy *phy) { struct eusb2_repeater *rptr = phy_get_drvdata(phy); return regulator_bulk_disable(rptr->cfg->num_vregs, rptr->vregs); } static const struct phy_ops eusb2_repeater_ops = { .init = eusb2_repeater_init, .exit = eusb2_repeater_exit, .set_mode = eusb2_repeater_set_mode, .owner = THIS_MODULE, }; static int eusb2_repeater_probe(struct platform_device *pdev) { struct eusb2_repeater *rptr; struct device *dev = &pdev->dev; struct phy_provider *phy_provider; struct device_node *np = dev->of_node; u32 res; int ret; rptr = devm_kzalloc(dev, sizeof(*rptr), GFP_KERNEL); if (!rptr) return -ENOMEM; rptr->dev = dev; dev_set_drvdata(dev, rptr); rptr->cfg = of_device_get_match_data(dev); if (!rptr->cfg) return -EINVAL; rptr->regmap = dev_get_regmap(dev->parent, NULL); if (!rptr->regmap) return -ENODEV; ret = of_property_read_u32(np, "reg", &res); if (ret < 0) return ret; rptr->base = res; ret = eusb2_repeater_init_vregs(rptr); if (ret < 0) { dev_err(dev, "unable to get supplies\n"); return ret; } rptr->phy = devm_phy_create(dev, np, &eusb2_repeater_ops); if (IS_ERR(rptr->phy)) { dev_err(dev, "failed to create PHY: %d\n", ret); return PTR_ERR(rptr->phy); } phy_set_drvdata(rptr->phy, rptr); phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); if (IS_ERR(phy_provider)) return PTR_ERR(phy_provider); dev_info(dev, "Registered Qcom-eUSB2 repeater\n"); return 0; } static void eusb2_repeater_remove(struct platform_device *pdev) { struct eusb2_repeater *rptr = platform_get_drvdata(pdev); if (!rptr) return; eusb2_repeater_exit(rptr->phy); } static const struct of_device_id eusb2_repeater_of_match_table[] = { { .compatible = "qcom,pm8550b-eusb2-repeater", .data = &pm8550b_eusb2_cfg, }, { .compatible = "qcom,smb2360-eusb2-repeater", .data = &smb2360_eusb2_cfg, }, { }, }; MODULE_DEVICE_TABLE(of, eusb2_repeater_of_match_table); static struct platform_driver eusb2_repeater_driver = { .probe = eusb2_repeater_probe, .remove = eusb2_repeater_remove, .driver = { .name = "qcom-eusb2-repeater", .of_match_table = eusb2_repeater_of_match_table, }, }; module_platform_driver(eusb2_repeater_driver); MODULE_DESCRIPTION("Qualcomm PMIC eUSB2 Repeater driver"); MODULE_LICENSE("GPL");