Commit 9b4a1915 authored by Bjorn Andersson's avatar Bjorn Andersson

Merge branch '20230201041853.1934355-1-quic_bjorande@quicinc.com' into drivers-for-6.3

parents c5d52d7b 080b4e24
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/soc/qcom/qcom,pmic-glink.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Qualcomm PMIC GLINK firmware interface for battery management, USB
Type-C and other things.
maintainers:
- Bjorn Andersson <andersson@kernel.org>
description:
The PMIC GLINK service, running on a coprocessor on some modern Qualcomm
platforms and implement USB Type-C handling and battery management. This
binding describes the component in the OS used to communicate with the
firmware and connect it's resources to those described in the Devicetree,
particularly the USB Type-C controllers relationship with USB and DisplayPort
components.
properties:
compatible:
items:
- enum:
- qcom,sc8180x-pmic-glink
- qcom,sc8280xp-pmic-glink
- qcom,sm8350-pmic-glink
- const: qcom,pmic-glink
'#address-cells':
const: 1
'#size-cells':
const: 0
patternProperties:
'^connector@\d$':
$ref: /schemas/connector/usb-connector.yaml#
properties:
reg: true
required:
- reg
unevaluatedProperties: false
required:
- compatible
additionalProperties: false
examples:
- |+
pmic-glink {
compatible = "qcom,sc8280xp-pmic-glink", "qcom,pmic-glink";
#address-cells = <1>;
#size-cells = <0>;
connector@0 {
compatible = "usb-c-connector";
reg = <0>;
power-role = "dual";
data-role = "dual";
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
endpoint {
remote-endpoint = <&usb_role>;
};
};
port@1 {
reg = <1>;
endpoint {
remote-endpoint = <&ss_phy_out>;
};
};
port@2 {
reg = <2>;
endpoint {
remote-endpoint = <&sbu_mux>;
};
};
};
};
};
...
......@@ -92,6 +92,21 @@ config QCOM_PDR_HELPERS
tristate
select QCOM_QMI_HELPERS
config QCOM_PMIC_GLINK
tristate "Qualcomm PMIC GLINK driver"
depends on RPMSG
depends on TYPEC
depends on DRM
select AUXILIARY_BUS
select QCOM_PDR_HELPERS
help
The Qualcomm PMIC GLINK driver provides access, over GLINK, to the
USB and battery firmware running on one of the coprocessors in
several modern Qualcomm platforms.
Say yes here to support USB-C and battery status on modern Qualcomm
platforms.
config QCOM_QMI_HELPERS
tristate
depends on NET
......
......@@ -8,6 +8,8 @@ obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o
obj-$(CONFIG_QCOM_MDT_LOADER) += mdt_loader.o
obj-$(CONFIG_QCOM_OCMEM) += ocmem.o
obj-$(CONFIG_QCOM_PDR_HELPERS) += pdr_interface.o
obj-$(CONFIG_QCOM_PMIC_GLINK) += pmic_glink.o
obj-$(CONFIG_QCOM_PMIC_GLINK) += pmic_glink_altmode.o
obj-$(CONFIG_QCOM_QMI_HELPERS) += qmi_helpers.o
qmi_helpers-y += qmi_encdec.o qmi_interface.o
obj-$(CONFIG_QCOM_RAMP_CTRL) += ramp_controller.o
......
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved.
* Copyright (c) 2022, Linaro Ltd
*/
#include <linux/auxiliary_bus.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/soc/qcom/pdr.h>
#include <linux/soc/qcom/pmic_glink.h>
struct pmic_glink {
struct device *dev;
struct pdr_handle *pdr;
struct rpmsg_endpoint *ept;
struct auxiliary_device altmode_aux;
struct auxiliary_device ps_aux;
struct auxiliary_device ucsi_aux;
/* serializing client_state and pdr_state updates */
struct mutex state_lock;
unsigned int client_state;
unsigned int pdr_state;
/* serializing clients list updates */
struct mutex client_lock;
struct list_head clients;
};
static struct pmic_glink *__pmic_glink;
static DEFINE_MUTEX(__pmic_glink_lock);
struct pmic_glink_client {
struct list_head node;
struct pmic_glink *pg;
unsigned int id;
void (*cb)(const void *data, size_t len, void *priv);
void (*pdr_notify)(void *priv, int state);
void *priv;
};
static void _devm_pmic_glink_release_client(struct device *dev, void *res)
{
struct pmic_glink_client *client = (struct pmic_glink_client *)res;
struct pmic_glink *pg = client->pg;
mutex_lock(&pg->client_lock);
list_del(&client->node);
mutex_unlock(&pg->client_lock);
}
struct pmic_glink_client *devm_pmic_glink_register_client(struct device *dev,
unsigned int id,
void (*cb)(const void *, size_t, void *),
void (*pdr)(void *, int),
void *priv)
{
struct pmic_glink_client *client;
struct pmic_glink *pg = dev_get_drvdata(dev->parent);
client = devres_alloc(_devm_pmic_glink_release_client, sizeof(*client), GFP_KERNEL);
if (!client)
return ERR_PTR(-ENOMEM);
client->pg = pg;
client->id = id;
client->cb = cb;
client->pdr_notify = pdr;
client->priv = priv;
mutex_lock(&pg->client_lock);
list_add(&client->node, &pg->clients);
mutex_unlock(&pg->client_lock);
devres_add(dev, client);
return client;
}
EXPORT_SYMBOL_GPL(devm_pmic_glink_register_client);
int pmic_glink_send(struct pmic_glink_client *client, void *data, size_t len)
{
struct pmic_glink *pg = client->pg;
return rpmsg_send(pg->ept, data, len);
}
EXPORT_SYMBOL_GPL(pmic_glink_send);
static int pmic_glink_rpmsg_callback(struct rpmsg_device *rpdev, void *data,
int len, void *priv, u32 addr)
{
struct pmic_glink_client *client;
struct pmic_glink_hdr *hdr;
struct pmic_glink *pg = dev_get_drvdata(&rpdev->dev);
if (len < sizeof(*hdr)) {
dev_warn(pg->dev, "ignoring truncated message\n");
return 0;
}
hdr = data;
list_for_each_entry(client, &pg->clients, node) {
if (client->id == le32_to_cpu(hdr->owner))
client->cb(data, len, client->priv);
}
return 0;
}
static void pmic_glink_aux_release(struct device *dev) {}
static int pmic_glink_add_aux_device(struct pmic_glink *pg,
struct auxiliary_device *aux,
const char *name)
{
struct device *parent = pg->dev;
int ret;
aux->name = name;
aux->dev.parent = parent;
aux->dev.release = pmic_glink_aux_release;
device_set_of_node_from_dev(&aux->dev, parent);
ret = auxiliary_device_init(aux);
if (ret)
return ret;
ret = auxiliary_device_add(aux);
if (ret)
auxiliary_device_uninit(aux);
return ret;
}
static void pmic_glink_del_aux_device(struct pmic_glink *pg,
struct auxiliary_device *aux)
{
auxiliary_device_delete(aux);
auxiliary_device_uninit(aux);
}
static void pmic_glink_state_notify_clients(struct pmic_glink *pg)
{
struct pmic_glink_client *client;
unsigned int new_state = pg->client_state;
if (pg->client_state != SERVREG_SERVICE_STATE_UP) {
if (pg->pdr_state == SERVREG_SERVICE_STATE_UP && pg->ept)
new_state = SERVREG_SERVICE_STATE_UP;
} else {
if (pg->pdr_state == SERVREG_SERVICE_STATE_UP && pg->ept)
new_state = SERVREG_SERVICE_STATE_DOWN;
}
if (new_state != pg->client_state) {
list_for_each_entry(client, &pg->clients, node)
client->pdr_notify(client->priv, new_state);
pg->client_state = new_state;
}
}
static void pmic_glink_pdr_callback(int state, char *svc_path, void *priv)
{
struct pmic_glink *pg = priv;
mutex_lock(&pg->state_lock);
pg->pdr_state = state;
pmic_glink_state_notify_clients(pg);
mutex_unlock(&pg->state_lock);
}
static int pmic_glink_rpmsg_probe(struct rpmsg_device *rpdev)
{
struct pmic_glink *pg = __pmic_glink;
int ret = 0;
mutex_lock(&__pmic_glink_lock);
if (!pg) {
ret = dev_err_probe(&rpdev->dev, -ENODEV, "no pmic_glink device to attach to\n");
goto out_unlock;
}
dev_set_drvdata(&rpdev->dev, pg);
mutex_lock(&pg->state_lock);
pg->ept = rpdev->ept;
pmic_glink_state_notify_clients(pg);
mutex_unlock(&pg->state_lock);
out_unlock:
mutex_unlock(&__pmic_glink_lock);
return ret;
}
static void pmic_glink_rpmsg_remove(struct rpmsg_device *rpdev)
{
struct pmic_glink *pg;
mutex_lock(&__pmic_glink_lock);
pg = __pmic_glink;
if (!pg)
goto out_unlock;
mutex_lock(&pg->state_lock);
pg->ept = NULL;
pmic_glink_state_notify_clients(pg);
mutex_unlock(&pg->state_lock);
out_unlock:
mutex_unlock(&__pmic_glink_lock);
}
static const struct rpmsg_device_id pmic_glink_rpmsg_id_match[] = {
{ "PMIC_RTR_ADSP_APPS" },
{}
};
static struct rpmsg_driver pmic_glink_rpmsg_driver = {
.probe = pmic_glink_rpmsg_probe,
.remove = pmic_glink_rpmsg_remove,
.callback = pmic_glink_rpmsg_callback,
.id_table = pmic_glink_rpmsg_id_match,
.drv = {
.name = "qcom_pmic_glink_rpmsg",
},
};
static int pmic_glink_probe(struct platform_device *pdev)
{
struct pdr_service *service;
struct pmic_glink *pg;
int ret;
pg = devm_kzalloc(&pdev->dev, sizeof(*pg), GFP_KERNEL);
if (!pg)
return -ENOMEM;
dev_set_drvdata(&pdev->dev, pg);
pg->dev = &pdev->dev;
INIT_LIST_HEAD(&pg->clients);
mutex_init(&pg->client_lock);
mutex_init(&pg->state_lock);
ret = pmic_glink_add_aux_device(pg, &pg->altmode_aux, "altmode");
if (ret)
return ret;
ret = pmic_glink_add_aux_device(pg, &pg->ps_aux, "power-supply");
if (ret)
goto out_release_altmode_aux;
pg->pdr = pdr_handle_alloc(pmic_glink_pdr_callback, pg);
if (IS_ERR(pg->pdr)) {
ret = dev_err_probe(&pdev->dev, PTR_ERR(pg->pdr), "failed to initialize pdr\n");
goto out_release_aux_devices;
}
service = pdr_add_lookup(pg->pdr, "tms/servreg", "msm/adsp/charger_pd");
if (IS_ERR(service)) {
ret = dev_err_probe(&pdev->dev, PTR_ERR(service),
"failed adding pdr lookup for charger_pd\n");
goto out_release_pdr_handle;
}
mutex_lock(&__pmic_glink_lock);
__pmic_glink = pg;
mutex_unlock(&__pmic_glink_lock);
return 0;
out_release_pdr_handle:
pdr_handle_release(pg->pdr);
out_release_aux_devices:
pmic_glink_del_aux_device(pg, &pg->ps_aux);
out_release_altmode_aux:
pmic_glink_del_aux_device(pg, &pg->altmode_aux);
return ret;
}
static int pmic_glink_remove(struct platform_device *pdev)
{
struct pmic_glink *pg = dev_get_drvdata(&pdev->dev);
pdr_handle_release(pg->pdr);
pmic_glink_del_aux_device(pg, &pg->ps_aux);
pmic_glink_del_aux_device(pg, &pg->altmode_aux);
mutex_lock(&__pmic_glink_lock);
__pmic_glink = NULL;
mutex_unlock(&__pmic_glink_lock);
return 0;
}
static const struct of_device_id pmic_glink_of_match[] = {
{ .compatible = "qcom,pmic-glink", },
{}
};
MODULE_DEVICE_TABLE(of, pmic_glink_of_match);
static struct platform_driver pmic_glink_driver = {
.probe = pmic_glink_probe,
.remove = pmic_glink_remove,
.driver = {
.name = "qcom_pmic_glink",
.of_match_table = pmic_glink_of_match,
},
};
static int pmic_glink_init(void)
{
platform_driver_register(&pmic_glink_driver);
register_rpmsg_driver(&pmic_glink_rpmsg_driver);
return 0;
};
module_init(pmic_glink_init);
static void pmic_glink_exit(void)
{
unregister_rpmsg_driver(&pmic_glink_rpmsg_driver);
platform_driver_unregister(&pmic_glink_driver);
};
module_exit(pmic_glink_exit);
MODULE_DESCRIPTION("Qualcomm PMIC GLINK driver");
MODULE_LICENSE("GPL");
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved.
* Copyright (c) 2022, Linaro Ltd
*/
#include <linux/auxiliary_bus.h>
#include <linux/bitfield.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/mutex.h>
#include <linux/property.h>
#include <linux/soc/qcom/pdr.h>
#include <drm/drm_bridge.h>
#include <linux/usb/typec_altmode.h>
#include <linux/usb/typec_dp.h>
#include <linux/usb/typec_mux.h>
#include <linux/soc/qcom/pmic_glink.h>
#define PMIC_GLINK_MAX_PORTS 2
#define USBC_SC8180X_NOTIFY_IND 0x13
#define USBC_CMD_WRITE_REQ 0x15
#define USBC_NOTIFY_IND 0x16
#define ALTMODE_PAN_EN 0x10
#define ALTMODE_PAN_ACK 0x11
struct usbc_write_req {
struct pmic_glink_hdr hdr;
__le32 cmd;
__le32 arg;
__le32 reserved;
};
#define NOTIFY_PAYLOAD_SIZE 16
struct usbc_notify {
struct pmic_glink_hdr hdr;
char payload[NOTIFY_PAYLOAD_SIZE];
u32 reserved;
};
struct usbc_sc8180x_notify {
struct pmic_glink_hdr hdr;
__le32 notification;
__le32 reserved[2];
};
enum pmic_glink_altmode_pin_assignment {
DPAM_HPD_OUT,
DPAM_HPD_A,
DPAM_HPD_B,
DPAM_HPD_C,
DPAM_HPD_D,
DPAM_HPD_E,
DPAM_HPD_F,
};
struct pmic_glink_altmode;
#define work_to_altmode_port(w) container_of((w), struct pmic_glink_altmode_port, work)
struct pmic_glink_altmode_port {
struct pmic_glink_altmode *altmode;
unsigned int index;
struct typec_switch *typec_switch;
struct typec_mux *typec_mux;
struct typec_mux_state state;
struct typec_altmode dp_alt;
struct work_struct work;
struct drm_bridge bridge;
enum typec_orientation orientation;
u16 svid;
u8 dp_data;
u8 mode;
u8 hpd_state;
u8 hpd_irq;
};
#define work_to_altmode(w) container_of((w), struct pmic_glink_altmode, enable_work)
struct pmic_glink_altmode {
struct device *dev;
unsigned int owner_id;
/* To synchronize WRITE_REQ acks */
struct mutex lock;
struct completion pan_ack;
struct pmic_glink_client *client;
struct work_struct enable_work;
struct pmic_glink_altmode_port ports[PMIC_GLINK_MAX_PORTS];
};
static int pmic_glink_altmode_request(struct pmic_glink_altmode *altmode, u32 cmd, u32 arg)
{
struct usbc_write_req req = {};
unsigned long left;
int ret;
/*
* The USBC_CMD_WRITE_REQ ack doesn't identify the request, so wait for
* one ack at a time.
*/
mutex_lock(&altmode->lock);
req.hdr.owner = cpu_to_le32(altmode->owner_id);
req.hdr.type = cpu_to_le32(PMIC_GLINK_REQ_RESP);
req.hdr.opcode = cpu_to_le32(USBC_CMD_WRITE_REQ);
req.cmd = cpu_to_le32(cmd);
req.arg = cpu_to_le32(arg);
ret = pmic_glink_send(altmode->client, &req, sizeof(req));
if (ret) {
dev_err(altmode->dev, "failed to send altmode request: %#x (%d)\n", cmd, ret);
goto out_unlock;
}
left = wait_for_completion_timeout(&altmode->pan_ack, 5 * HZ);
if (!left) {
dev_err(altmode->dev, "timeout waiting for altmode request ack for: %#x\n", cmd);
ret = -ETIMEDOUT;
}
out_unlock:
mutex_unlock(&altmode->lock);
return ret;
}
static void pmic_glink_altmode_enable_dp(struct pmic_glink_altmode *altmode,
struct pmic_glink_altmode_port *port,
u8 mode, bool hpd_state,
bool hpd_irq)
{
struct typec_displayport_data dp_data = {};
int ret;
dp_data.status = DP_STATUS_ENABLED;
if (hpd_state)
dp_data.status |= DP_STATUS_HPD_STATE;
if (hpd_irq)
dp_data.status |= DP_STATUS_IRQ_HPD;
dp_data.conf = DP_CONF_SET_PIN_ASSIGN(mode);
port->state.alt = &port->dp_alt;
port->state.data = &dp_data;
port->state.mode = TYPEC_MODAL_STATE(mode);
ret = typec_mux_set(port->typec_mux, &port->state);
if (ret)
dev_err(altmode->dev, "failed to switch mux to DP\n");
}
static void pmic_glink_altmode_enable_usb(struct pmic_glink_altmode *altmode,
struct pmic_glink_altmode_port *port)
{
int ret;
port->state.alt = NULL;
port->state.data = NULL;
port->state.mode = TYPEC_STATE_USB;
ret = typec_mux_set(port->typec_mux, &port->state);
if (ret)
dev_err(altmode->dev, "failed to switch mux to USB\n");
}
static void pmic_glink_altmode_worker(struct work_struct *work)
{
struct pmic_glink_altmode_port *alt_port = work_to_altmode_port(work);
struct pmic_glink_altmode *altmode = alt_port->altmode;
typec_switch_set(alt_port->typec_switch, alt_port->orientation);
if (alt_port->svid == USB_TYPEC_DP_SID)
pmic_glink_altmode_enable_dp(altmode, alt_port, alt_port->mode,
alt_port->hpd_state, alt_port->hpd_irq);
else
pmic_glink_altmode_enable_usb(altmode, alt_port);
if (alt_port->hpd_state)
drm_bridge_hpd_notify(&alt_port->bridge, connector_status_connected);
else
drm_bridge_hpd_notify(&alt_port->bridge, connector_status_disconnected);
pmic_glink_altmode_request(altmode, ALTMODE_PAN_ACK, alt_port->index);
};
static enum typec_orientation pmic_glink_altmode_orientation(unsigned int orientation)
{
if (orientation == 0)
return TYPEC_ORIENTATION_NORMAL;
else if (orientation == 1)
return TYPEC_ORIENTATION_REVERSE;
else
return TYPEC_ORIENTATION_NONE;
}
#define SC8180X_PORT_MASK 0x000000ff
#define SC8180X_ORIENTATION_MASK 0x0000ff00
#define SC8180X_MUX_MASK 0x00ff0000
#define SC8180X_MODE_MASK 0x3f000000
#define SC8180X_HPD_STATE_MASK 0x40000000
#define SC8180X_HPD_IRQ_MASK 0x80000000
static void pmic_glink_altmode_sc8180xp_notify(struct pmic_glink_altmode *altmode,
const void *data, size_t len)
{
struct pmic_glink_altmode_port *alt_port;
const struct usbc_sc8180x_notify *msg;
u32 notification;
u8 orientation;
u8 hpd_state;
u8 hpd_irq;
u16 svid;
u8 port;
u8 mode;
u8 mux;
if (len != sizeof(*msg)) {
dev_warn(altmode->dev, "invalid length of USBC_NOTIFY indication: %zd\n", len);
return;
}
msg = data;
notification = le32_to_cpu(msg->notification);
port = FIELD_GET(SC8180X_PORT_MASK, notification);
orientation = FIELD_GET(SC8180X_ORIENTATION_MASK, notification);
mux = FIELD_GET(SC8180X_MUX_MASK, notification);
mode = FIELD_GET(SC8180X_MODE_MASK, notification);
hpd_state = FIELD_GET(SC8180X_HPD_STATE_MASK, notification);
hpd_irq = FIELD_GET(SC8180X_HPD_IRQ_MASK, notification);
svid = mux == 2 ? USB_TYPEC_DP_SID : 0;
if (!altmode->ports[port].altmode) {
dev_dbg(altmode->dev, "notification on undefined port %d\n", port);
return;
}
alt_port = &altmode->ports[port];
alt_port->orientation = pmic_glink_altmode_orientation(orientation);
alt_port->svid = mux == 2 ? USB_TYPEC_DP_SID : 0;
alt_port->mode = mode;
alt_port->hpd_state = hpd_state;
alt_port->hpd_irq = hpd_irq;
schedule_work(&alt_port->work);
}
#define SC8280XP_DPAM_MASK 0x3f
#define SC8280XP_HPD_STATE_MASK BIT(6)
#define SC8280XP_HPD_IRQ_MASK BIT(7)
static void pmic_glink_altmode_sc8280xp_notify(struct pmic_glink_altmode *altmode,
u16 svid, const void *data, size_t len)
{
struct pmic_glink_altmode_port *alt_port;
const struct usbc_notify *notify;
u8 orientation;
u8 hpd_state;
u8 hpd_irq;
u8 mode;
u8 port;
if (len != sizeof(*notify)) {
dev_warn(altmode->dev, "invalid length USBC_NOTIFY_IND: %zd\n",
len);
return;
}
notify = data;
port = notify->payload[0];
orientation = notify->payload[1];
mode = FIELD_GET(SC8280XP_DPAM_MASK, notify->payload[8]) - DPAM_HPD_A;
hpd_state = FIELD_GET(SC8280XP_HPD_STATE_MASK, notify->payload[8]);
hpd_irq = FIELD_GET(SC8280XP_HPD_IRQ_MASK, notify->payload[8]);
if (!altmode->ports[port].altmode) {
dev_dbg(altmode->dev, "notification on undefined port %d\n", port);
return;
}
alt_port = &altmode->ports[port];
alt_port->orientation = pmic_glink_altmode_orientation(orientation);
alt_port->svid = svid;
alt_port->mode = mode;
alt_port->hpd_state = hpd_state;
alt_port->hpd_irq = hpd_irq;
schedule_work(&alt_port->work);
}
static void pmic_glink_altmode_callback(const void *data, size_t len, void *priv)
{
struct pmic_glink_altmode *altmode = priv;
const struct pmic_glink_hdr *hdr = data;
u16 opcode;
u16 svid;
opcode = le32_to_cpu(hdr->opcode) & 0xff;
svid = le32_to_cpu(hdr->opcode) >> 16;
switch (opcode) {
case USBC_CMD_WRITE_REQ:
complete(&altmode->pan_ack);
break;
case USBC_NOTIFY_IND:
pmic_glink_altmode_sc8280xp_notify(altmode, svid, data, len);
break;
case USBC_SC8180X_NOTIFY_IND:
pmic_glink_altmode_sc8180xp_notify(altmode, data, len);
break;
}
}
static int pmic_glink_altmode_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 pmic_glink_altmode_bridge_funcs = {
.attach = pmic_glink_altmode_attach,
};
static void pmic_glink_altmode_put_mux(void *data)
{
typec_mux_put(data);
}
static void pmic_glink_altmode_put_switch(void *data)
{
typec_switch_put(data);
}
static void pmic_glink_altmode_enable_worker(struct work_struct *work)
{
struct pmic_glink_altmode *altmode = work_to_altmode(work);
int ret;
ret = pmic_glink_altmode_request(altmode, ALTMODE_PAN_EN, 0);
if (ret)
dev_err(altmode->dev, "failed to request altmode notifications\n");
}
static void pmic_glink_altmode_pdr_notify(void *priv, int state)
{
struct pmic_glink_altmode *altmode = priv;
if (state == SERVREG_SERVICE_STATE_UP)
schedule_work(&altmode->enable_work);
}
static const struct of_device_id pmic_glink_altmode_of_quirks[] = {
{ .compatible = "qcom,sc8180x-pmic-glink", .data = (void *)PMIC_GLINK_OWNER_USBC },
{}
};
static int pmic_glink_altmode_probe(struct auxiliary_device *adev,
const struct auxiliary_device_id *id)
{
struct pmic_glink_altmode_port *alt_port;
struct pmic_glink_altmode *altmode;
struct typec_altmode_desc mux_desc = {};
const struct of_device_id *match;
struct fwnode_handle *fwnode;
struct device *dev = &adev->dev;
u32 port;
int ret;
altmode = devm_kzalloc(dev, sizeof(*altmode), GFP_KERNEL);
if (!altmode)
return -ENOMEM;
altmode->dev = dev;
match = of_match_device(pmic_glink_altmode_of_quirks, dev->parent);
if (match)
altmode->owner_id = (unsigned long)match->data;
else
altmode->owner_id = PMIC_GLINK_OWNER_USBC_PAN;
INIT_WORK(&altmode->enable_work, pmic_glink_altmode_enable_worker);
init_completion(&altmode->pan_ack);
mutex_init(&altmode->lock);
device_for_each_child_node(dev, fwnode) {
ret = fwnode_property_read_u32(fwnode, "reg", &port);
if (ret < 0) {
dev_err(dev, "missing reg property of %pOFn\n", fwnode);
return ret;
}
if (port >= ARRAY_SIZE(altmode->ports)) {
dev_warn(dev, "invalid connector number, ignoring\n");
continue;
}
if (altmode->ports[port].altmode) {
dev_err(dev, "multiple connector definition for port %u\n", port);
return -EINVAL;
}
alt_port = &altmode->ports[port];
alt_port->altmode = altmode;
alt_port->index = port;
INIT_WORK(&alt_port->work, pmic_glink_altmode_worker);
alt_port->bridge.funcs = &pmic_glink_altmode_bridge_funcs;
alt_port->bridge.of_node = to_of_node(fwnode);
alt_port->bridge.ops = DRM_BRIDGE_OP_HPD;
alt_port->bridge.type = DRM_MODE_CONNECTOR_USB;
ret = devm_drm_bridge_add(dev, &alt_port->bridge);
if (ret)
return ret;
alt_port->dp_alt.svid = USB_TYPEC_DP_SID;
alt_port->dp_alt.mode = USB_TYPEC_DP_MODE;
alt_port->dp_alt.active = 1;
mux_desc.svid = USB_TYPEC_DP_SID;
mux_desc.mode = USB_TYPEC_DP_MODE;
alt_port->typec_mux = fwnode_typec_mux_get(fwnode, &mux_desc);
if (IS_ERR(alt_port->typec_mux))
return dev_err_probe(dev, PTR_ERR(alt_port->typec_mux),
"failed to acquire mode-switch for port: %d\n",
port);
ret = devm_add_action_or_reset(dev, pmic_glink_altmode_put_mux,
alt_port->typec_mux);
if (ret)
return ret;
alt_port->typec_switch = fwnode_typec_switch_get(fwnode);
if (IS_ERR(alt_port->typec_switch))
return dev_err_probe(dev, PTR_ERR(alt_port->typec_switch),
"failed to acquire orientation-switch for port: %d\n",
port);
ret = devm_add_action_or_reset(dev, pmic_glink_altmode_put_switch,
alt_port->typec_switch);
if (ret)
return ret;
}
altmode->client = devm_pmic_glink_register_client(dev,
altmode->owner_id,
pmic_glink_altmode_callback,
pmic_glink_altmode_pdr_notify,
altmode);
return PTR_ERR_OR_ZERO(altmode->client);
}
static const struct auxiliary_device_id pmic_glink_altmode_id_table[] = {
{ .name = "pmic_glink.altmode", },
{},
};
MODULE_DEVICE_TABLE(auxiliary, pmic_glink_altmode_id_table);
static struct auxiliary_driver pmic_glink_altmode_driver = {
.name = "pmic_glink_altmode",
.probe = pmic_glink_altmode_probe,
.id_table = pmic_glink_altmode_id_table,
};
module_auxiliary_driver(pmic_glink_altmode_driver);
MODULE_DESCRIPTION("Qualcomm PMIC GLINK Altmode driver");
MODULE_LICENSE("GPL");
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2022, Linaro Ltd
*/
#ifndef __SOC_QCOM_PMIC_GLINK_H__
#define __SOC_QCOM_PMIC_GLINK_H__
struct pmic_glink;
struct pmic_glink_client;
#define PMIC_GLINK_OWNER_BATTMGR 32778
#define PMIC_GLINK_OWNER_USBC 32779
#define PMIC_GLINK_OWNER_USBC_PAN 32780
#define PMIC_GLINK_REQ_RESP 1
#define PMIC_GLINK_NOTIFY 2
struct pmic_glink_hdr {
__le32 owner;
__le32 type;
__le32 opcode;
};
int pmic_glink_send(struct pmic_glink_client *client, void *data, size_t len);
struct pmic_glink_client *devm_pmic_glink_register_client(struct device *dev,
unsigned int id,
void (*cb)(const void *, size_t, void *),
void (*pdr)(void *, int),
void *priv);
#endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment