// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2023, Linaro Ltd. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "qcom_pmic_typec_pdphy.h" #include "qcom_pmic_typec_port.h" struct pmic_typec_resources { struct pmic_typec_pdphy_resources *pdphy_res; struct pmic_typec_port_resources *port_res; }; struct pmic_typec { struct device *dev; struct tcpm_port *tcpm_port; struct tcpc_dev tcpc; struct pmic_typec_pdphy *pmic_typec_pdphy; struct pmic_typec_port *pmic_typec_port; bool vbus_enabled; struct mutex lock; /* VBUS state serialization */ }; #define tcpc_to_tcpm(_tcpc_) container_of(_tcpc_, struct pmic_typec, tcpc) static int qcom_pmic_typec_get_vbus(struct tcpc_dev *tcpc) { struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); int ret; mutex_lock(&tcpm->lock); ret = tcpm->vbus_enabled || qcom_pmic_typec_port_get_vbus(tcpm->pmic_typec_port); mutex_unlock(&tcpm->lock); return ret; } static int qcom_pmic_typec_set_vbus(struct tcpc_dev *tcpc, bool on, bool sink) { struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); int ret = 0; mutex_lock(&tcpm->lock); if (tcpm->vbus_enabled == on) goto done; ret = qcom_pmic_typec_port_set_vbus(tcpm->pmic_typec_port, on); if (ret) goto done; tcpm->vbus_enabled = on; tcpm_vbus_change(tcpm->tcpm_port); done: dev_dbg(tcpm->dev, "set_vbus set: %d result %d\n", on, ret); mutex_unlock(&tcpm->lock); return ret; } static int qcom_pmic_typec_set_vconn(struct tcpc_dev *tcpc, bool on) { struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); return qcom_pmic_typec_port_set_vconn(tcpm->pmic_typec_port, on); } static int qcom_pmic_typec_get_cc(struct tcpc_dev *tcpc, enum typec_cc_status *cc1, enum typec_cc_status *cc2) { struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); return qcom_pmic_typec_port_get_cc(tcpm->pmic_typec_port, cc1, cc2); } static int qcom_pmic_typec_set_cc(struct tcpc_dev *tcpc, enum typec_cc_status cc) { struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); return qcom_pmic_typec_port_set_cc(tcpm->pmic_typec_port, cc); } static int qcom_pmic_typec_set_polarity(struct tcpc_dev *tcpc, enum typec_cc_polarity pol) { /* Polarity is set separately by phy-qcom-qmp.c */ return 0; } static int qcom_pmic_typec_start_toggling(struct tcpc_dev *tcpc, enum typec_port_type port_type, enum typec_cc_status cc) { struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); return qcom_pmic_typec_port_start_toggling(tcpm->pmic_typec_port, port_type, cc); } static int qcom_pmic_typec_set_roles(struct tcpc_dev *tcpc, bool attached, enum typec_role power_role, enum typec_data_role data_role) { struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); return qcom_pmic_typec_pdphy_set_roles(tcpm->pmic_typec_pdphy, data_role, power_role); } static int qcom_pmic_typec_set_pd_rx(struct tcpc_dev *tcpc, bool on) { struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); return qcom_pmic_typec_pdphy_set_pd_rx(tcpm->pmic_typec_pdphy, on); } static int qcom_pmic_typec_pd_transmit(struct tcpc_dev *tcpc, enum tcpm_transmit_type type, const struct pd_message *msg, unsigned int negotiated_rev) { struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); return qcom_pmic_typec_pdphy_pd_transmit(tcpm->pmic_typec_pdphy, type, msg, negotiated_rev); } static int qcom_pmic_typec_init(struct tcpc_dev *tcpc) { return 0; } static int qcom_pmic_typec_probe(struct platform_device *pdev) { struct pmic_typec *tcpm; struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; const struct pmic_typec_resources *res; struct regmap *regmap; struct device *bridge_dev; u32 base[2]; int ret; res = of_device_get_match_data(dev); if (!res) return -ENODEV; tcpm = devm_kzalloc(dev, sizeof(*tcpm), GFP_KERNEL); if (!tcpm) return -ENOMEM; tcpm->dev = dev; tcpm->tcpc.init = qcom_pmic_typec_init; tcpm->tcpc.get_vbus = qcom_pmic_typec_get_vbus; tcpm->tcpc.set_vbus = qcom_pmic_typec_set_vbus; tcpm->tcpc.set_cc = qcom_pmic_typec_set_cc; tcpm->tcpc.get_cc = qcom_pmic_typec_get_cc; tcpm->tcpc.set_polarity = qcom_pmic_typec_set_polarity; tcpm->tcpc.set_vconn = qcom_pmic_typec_set_vconn; tcpm->tcpc.start_toggling = qcom_pmic_typec_start_toggling; tcpm->tcpc.set_pd_rx = qcom_pmic_typec_set_pd_rx; tcpm->tcpc.set_roles = qcom_pmic_typec_set_roles; tcpm->tcpc.pd_transmit = qcom_pmic_typec_pd_transmit; regmap = dev_get_regmap(dev->parent, NULL); if (!regmap) { dev_err(dev, "Failed to get regmap\n"); return -ENODEV; } ret = of_property_read_u32_array(np, "reg", base, 2); if (ret) return ret; tcpm->pmic_typec_port = qcom_pmic_typec_port_alloc(dev); if (IS_ERR(tcpm->pmic_typec_port)) return PTR_ERR(tcpm->pmic_typec_port); tcpm->pmic_typec_pdphy = qcom_pmic_typec_pdphy_alloc(dev); if (IS_ERR(tcpm->pmic_typec_pdphy)) return PTR_ERR(tcpm->pmic_typec_pdphy); ret = qcom_pmic_typec_port_probe(pdev, tcpm->pmic_typec_port, res->port_res, regmap, base[0]); if (ret) return ret; ret = qcom_pmic_typec_pdphy_probe(pdev, tcpm->pmic_typec_pdphy, res->pdphy_res, regmap, base[1]); if (ret) return ret; mutex_init(&tcpm->lock); platform_set_drvdata(pdev, tcpm); tcpm->tcpc.fwnode = device_get_named_child_node(tcpm->dev, "connector"); if (!tcpm->tcpc.fwnode) return -EINVAL; bridge_dev = drm_dp_hpd_bridge_register(tcpm->dev, to_of_node(tcpm->tcpc.fwnode)); if (IS_ERR(bridge_dev)) return PTR_ERR(bridge_dev); tcpm->tcpm_port = tcpm_register_port(tcpm->dev, &tcpm->tcpc); if (IS_ERR(tcpm->tcpm_port)) { ret = PTR_ERR(tcpm->tcpm_port); goto fwnode_remove; } ret = qcom_pmic_typec_port_start(tcpm->pmic_typec_port, tcpm->tcpm_port); if (ret) goto fwnode_remove; ret = qcom_pmic_typec_pdphy_start(tcpm->pmic_typec_pdphy, tcpm->tcpm_port); if (ret) goto fwnode_remove; return 0; fwnode_remove: fwnode_remove_software_node(tcpm->tcpc.fwnode); return ret; } static void qcom_pmic_typec_remove(struct platform_device *pdev) { struct pmic_typec *tcpm = platform_get_drvdata(pdev); qcom_pmic_typec_pdphy_stop(tcpm->pmic_typec_pdphy); qcom_pmic_typec_port_stop(tcpm->pmic_typec_port); tcpm_unregister_port(tcpm->tcpm_port); fwnode_remove_software_node(tcpm->tcpc.fwnode); } static struct pmic_typec_pdphy_resources pm8150b_pdphy_res = { .irq_params = { { .virq = PMIC_PDPHY_SIG_TX_IRQ, .irq_name = "sig-tx", }, { .virq = PMIC_PDPHY_SIG_RX_IRQ, .irq_name = "sig-rx", }, { .virq = PMIC_PDPHY_MSG_TX_IRQ, .irq_name = "msg-tx", }, { .virq = PMIC_PDPHY_MSG_RX_IRQ, .irq_name = "msg-rx", }, { .virq = PMIC_PDPHY_MSG_TX_FAIL_IRQ, .irq_name = "msg-tx-failed", }, { .virq = PMIC_PDPHY_MSG_TX_DISCARD_IRQ, .irq_name = "msg-tx-discarded", }, { .virq = PMIC_PDPHY_MSG_RX_DISCARD_IRQ, .irq_name = "msg-rx-discarded", }, }, .nr_irqs = 7, }; static struct pmic_typec_port_resources pm8150b_port_res = { .irq_params = { { .irq_name = "vpd-detect", .virq = PMIC_TYPEC_VPD_IRQ, }, { .irq_name = "cc-state-change", .virq = PMIC_TYPEC_CC_STATE_IRQ, }, { .irq_name = "vconn-oc", .virq = PMIC_TYPEC_VCONN_OC_IRQ, }, { .irq_name = "vbus-change", .virq = PMIC_TYPEC_VBUS_IRQ, }, { .irq_name = "attach-detach", .virq = PMIC_TYPEC_ATTACH_DETACH_IRQ, }, { .irq_name = "legacy-cable-detect", .virq = PMIC_TYPEC_LEGACY_CABLE_IRQ, }, { .irq_name = "try-snk-src-detect", .virq = PMIC_TYPEC_TRY_SNK_SRC_IRQ, }, }, .nr_irqs = 7, }; static struct pmic_typec_resources pm8150b_typec_res = { .pdphy_res = &pm8150b_pdphy_res, .port_res = &pm8150b_port_res, }; static const struct of_device_id qcom_pmic_typec_table[] = { { .compatible = "qcom,pm8150b-typec", .data = &pm8150b_typec_res }, { } }; MODULE_DEVICE_TABLE(of, qcom_pmic_typec_table); static struct platform_driver qcom_pmic_typec_driver = { .driver = { .name = "qcom,pmic-typec", .of_match_table = qcom_pmic_typec_table, }, .probe = qcom_pmic_typec_probe, .remove_new = qcom_pmic_typec_remove, }; module_platform_driver(qcom_pmic_typec_driver); MODULE_DESCRIPTION("QCOM PMIC USB Type-C Port Manager Driver"); MODULE_LICENSE("GPL");