blob: 1a2b4bddaa97e86da87b18ae5174413e3238af6a [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2023, Linaro Ltd. All rights reserved.
*/
#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/bridge/aux-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; /* 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");