#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
#include <linux/usb/role.h>
#include <linux/usb/tcpm.h>
#include <linux/usb/typec_mux.h>
#include <drm/drm_bridge.h>
#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;
struct drm_bridge bridge;
};
#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)
{
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;
}
#if IS_ENABLED(CONFIG_DRM)
static int qcom_pmic_typec_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{
return flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR ? 0 : -EINVAL;
}
static const struct drm_bridge_funcs qcom_pmic_typec_bridge_funcs = {
.attach = qcom_pmic_typec_attach,
};
static int qcom_pmic_typec_init_drm(struct pmic_typec *tcpm)
{
tcpm->bridge.funcs = &qcom_pmic_typec_bridge_funcs;
#ifdef CONFIG_OF
tcpm->bridge.of_node = of_get_child_by_name(tcpm->dev->of_node, "connector");
#endif
tcpm->bridge.ops = DRM_BRIDGE_OP_HPD;
tcpm->bridge.type = DRM_MODE_CONNECTOR_DisplayPort;
return devm_drm_bridge_add(tcpm->dev, &tcpm->bridge);
}
#else
static int qcom_pmic_typec_init_drm(struct pmic_typec *tcpm)
{
return 0;
}
#endif
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;
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);
ret = qcom_pmic_typec_init_drm(tcpm);
if (ret)
return ret;
tcpm->tcpc.fwnode = device_get_named_child_node(tcpm->dev, "connector");
if (!tcpm->tcpc.fwnode)
return -EINVAL;
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"