From patchwork Mon Jan 30 04:20:00 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Bjorn Andersson X-Patchwork-Id: 648737 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id B8C5EC636D0 for ; Mon, 30 Jan 2023 04:20:18 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S235345AbjA3EUR (ORCPT ); Sun, 29 Jan 2023 23:20:17 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:33774 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229769AbjA3EUO (ORCPT ); Sun, 29 Jan 2023 23:20:14 -0500 Received: from mx0a-0031df01.pphosted.com (mx0a-0031df01.pphosted.com [205.220.168.131]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 99F881E9CD; Sun, 29 Jan 2023 20:20:12 -0800 (PST) Received: from pps.filterd (m0279863.ppops.net [127.0.0.1]) by mx0a-0031df01.pphosted.com (8.17.1.19/8.17.1.19) with ESMTP id 30U33VBf004932; Mon, 30 Jan 2023 04:20:10 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=quicinc.com; h=from : to : cc : subject : date : message-id : in-reply-to : references : mime-version : content-transfer-encoding : content-type; s=qcppdkim1; bh=6INT/obqK4IGGCW5l0lgxR7ZnjQ1EW8DKJVIRWiKt7o=; b=U97o8ZMvTBXZL1dttJCtgriIzuMWFlUGv1H/gWxNEqCBRzKSOjLDwwEsLAh2pRO1euRY FjI2qfUcHkv9UfBNXVjJIt9t2SbWeBKM5iNeu5VRQeWX1XzJ9giD5O4OwnH5ywN/5q3f AQmVSC2t3bA9fV3oYotqVeiCTD6/xaQD5Wi0kkzgZRAIFzGgMj/EMtmLpapYtZI+Uh/t gN+Zw04Jv779jAzRr0lnRPeMQdd4Hnpd0ugq7GB0EmqG4INtVW5+s466Mfdz1rQdpzIy GD27YY27rDVblEgqGsocz6t/f73xnKNwcRkZBTAULN1EnqzSZRY0W4+wS1ANlY52DPTW jA== Received: from nalasppmta04.qualcomm.com (Global_NAT1.qualcomm.com [129.46.96.20]) by mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 3ncuxajps3-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Mon, 30 Jan 2023 04:20:09 +0000 Received: from nalasex01c.na.qualcomm.com (nalasex01c.na.qualcomm.com [10.47.97.35]) by NALASPPMTA04.qualcomm.com (8.17.1.5/8.17.1.5) with ESMTPS id 30U4K8VM004612 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Mon, 30 Jan 2023 04:20:08 GMT Received: from hu-bjorande-lv.qualcomm.com (10.49.16.6) by nalasex01c.na.qualcomm.com (10.47.97.35) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.986.36; Sun, 29 Jan 2023 20:20:08 -0800 From: Bjorn Andersson To: Andy Gross , Bjorn Andersson , Konrad Dybcio , Rob Herring , Krzysztof Kozlowski , Sebastian Reichel CC: , , , , "Subbaraman Narayanamurthy" , Johan Hovold , Neil Armstrong Subject: [PATCH v3 1/4] dt-bindings: soc: qcom: Introduce PMIC GLINK binding Date: Sun, 29 Jan 2023 20:20:00 -0800 Message-ID: <20230130042003.577063-2-quic_bjorande@quicinc.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20230130042003.577063-1-quic_bjorande@quicinc.com> References: <20230130042003.577063-1-quic_bjorande@quicinc.com> MIME-Version: 1.0 X-Originating-IP: [10.49.16.6] X-ClientProxiedBy: nalasex01b.na.qualcomm.com (10.47.209.197) To nalasex01c.na.qualcomm.com (10.47.97.35) X-QCInternal: smtphost X-Proofpoint-Virus-Version: vendor=nai engine=6200 definitions=5800 signatures=585085 X-Proofpoint-ORIG-GUID: GOQlo82ecIJ2A9ddiR9yKfDQduMpLbC_ X-Proofpoint-GUID: GOQlo82ecIJ2A9ddiR9yKfDQduMpLbC_ X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.219,Aquarius:18.0.930,Hydra:6.0.562,FMLib:17.11.122.1 definitions=2023-01-30_02,2023-01-27_01,2022-06-22_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 suspectscore=0 spamscore=0 malwarescore=0 bulkscore=0 clxscore=1015 priorityscore=1501 adultscore=0 lowpriorityscore=0 mlxlogscore=999 impostorscore=0 phishscore=0 mlxscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.12.0-2212070000 definitions=main-2301300040 Precedence: bulk List-ID: X-Mailing-List: linux-arm-msm@vger.kernel.org From: Bjorn Andersson 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. Signed-off-by: Bjorn Andersson Tested-by: Konrad Dybcio # SM8350 PDX215 Signed-off-by: Bjorn Andersson --- Changes since v2: - connector/reg is marked required .../bindings/soc/qcom/qcom,pmic-glink.yaml | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml b/Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml new file mode 100644 index 000000000000..e7cdd9ee24ec --- /dev/null +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml @@ -0,0 +1,105 @@ +# 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 + +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>; + + #address-cells = <1>; + #size-cells = <0>; + + endpoint@0 { + reg = <0>; + remote-endpoint = <&qmp_out>; + }; + + endpoint@1 { + reg = <1>; + remote-endpoint = <&displayport_hpd>; + }; + }; + + port@2 { + reg = <2>; + endpoint { + remote-endpoint = <&sbu_mux>; + }; + }; + }; + }; + }; +... + From patchwork Mon Jan 30 04:20:01 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Bjorn Andersson X-Patchwork-Id: 649007 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id B4C1EC636D7 for ; Mon, 30 Jan 2023 04:20:19 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S235393AbjA3EUS (ORCPT ); Sun, 29 Jan 2023 23:20:18 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:33786 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S235082AbjA3EUQ (ORCPT ); Sun, 29 Jan 2023 23:20:16 -0500 Received: from mx0a-0031df01.pphosted.com (mx0a-0031df01.pphosted.com [205.220.168.131]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id B97B41F5D1; Sun, 29 Jan 2023 20:20:14 -0800 (PST) Received: from pps.filterd (m0279867.ppops.net [127.0.0.1]) by mx0a-0031df01.pphosted.com (8.17.1.19/8.17.1.19) with ESMTP id 30U4CrYF012819; Mon, 30 Jan 2023 04:20:10 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=quicinc.com; h=from : to : cc : subject : date : message-id : in-reply-to : references : mime-version : content-transfer-encoding : content-type; s=qcppdkim1; bh=eAMbBW9mcm429jSXrJgqyUTXGjVOmLCF7Kb5wND5C6Y=; b=GR1xFjt/CD6WUKrvZRWMwowPezCCv+gtDkJzpTHh6bXdWv193OIiypAFSukL2xLN/Jvn yeMnupJw/1jC5E8GnkCaYqBet41BK1GbJJegIqFD6tCpAXCHVtUxhULwv6jOEjbEWVW4 M6Vzne/a867Bzkw08EK3821cqBULkkkzEuW8KDIR+CsEFOzGisLkNzUZx7foGkipYhGW o2MR6JEyz2vLXqxB6+99wHZt9q77WRq/mXE3jWzNCoW5YYIdUIehIsnYvej1axyXLZTi 0JJTVQI9M+EJqJizduIjOgt5T8Y7ovKgwScbNa/2J/pAlKXEKqGGS+YUe9BBE7wtt/3E Zw== Received: from nalasppmta01.qualcomm.com (Global_NAT1.qualcomm.com [129.46.96.20]) by mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 3ncsdptvhj-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Mon, 30 Jan 2023 04:20:10 +0000 Received: from nalasex01c.na.qualcomm.com (nalasex01c.na.qualcomm.com [10.47.97.35]) by NALASPPMTA01.qualcomm.com (8.17.1.5/8.17.1.5) with ESMTPS id 30U4K9iS021487 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Mon, 30 Jan 2023 04:20:09 GMT Received: from hu-bjorande-lv.qualcomm.com (10.49.16.6) by nalasex01c.na.qualcomm.com (10.47.97.35) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.986.36; Sun, 29 Jan 2023 20:20:08 -0800 From: Bjorn Andersson To: Andy Gross , Bjorn Andersson , Konrad Dybcio , Rob Herring , Krzysztof Kozlowski , Sebastian Reichel CC: , , , , "Subbaraman Narayanamurthy" , Johan Hovold , Neil Armstrong Subject: [PATCH v3 2/4] soc: qcom: pmic_glink: Introduce base PMIC GLINK driver Date: Sun, 29 Jan 2023 20:20:01 -0800 Message-ID: <20230130042003.577063-3-quic_bjorande@quicinc.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20230130042003.577063-1-quic_bjorande@quicinc.com> References: <20230130042003.577063-1-quic_bjorande@quicinc.com> MIME-Version: 1.0 X-Originating-IP: [10.49.16.6] X-ClientProxiedBy: nalasex01b.na.qualcomm.com (10.47.209.197) To nalasex01c.na.qualcomm.com (10.47.97.35) X-QCInternal: smtphost X-Proofpoint-Virus-Version: vendor=nai engine=6200 definitions=5800 signatures=585085 X-Proofpoint-GUID: FRuIvIit3heBodmZTOAZt1bo7ijrvv79 X-Proofpoint-ORIG-GUID: FRuIvIit3heBodmZTOAZt1bo7ijrvv79 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.219,Aquarius:18.0.930,Hydra:6.0.562,FMLib:17.11.122.1 definitions=2023-01-30_02,2023-01-27_01,2022-06-22_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 malwarescore=0 lowpriorityscore=0 bulkscore=0 adultscore=0 suspectscore=0 mlxlogscore=999 spamscore=0 clxscore=1015 impostorscore=0 mlxscore=0 phishscore=0 priorityscore=1501 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.12.0-2212070000 definitions=main-2301300040 Precedence: bulk List-ID: X-Mailing-List: linux-arm-msm@vger.kernel.org From: Bjorn Andersson The PMIC GLINK service runs on one of the co-processors of some modern Qualcomm platforms and implements USB-C and battery managements. It uses a message based protocol over GLINK for communication with the OS, hence the name. The driver implemented provides the rpmsg device for communication and uses auxiliary bus to spawn off individual devices in respective subsystem. The auxiliary devices are spawned off from a platform_device, so that the drm_bridge is available early, to allow the DisplayPort driver to probe even before the remoteproc has spun up. Signed-off-by: Bjorn Andersson Tested-by: Konrad Dybcio # SM8350 PDX215 Signed-off-by: Bjorn Andersson --- Changes since v2: - None drivers/soc/qcom/Kconfig | 15 ++ drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/pmic_glink.c | 336 ++++++++++++++++++++++++++++ include/linux/soc/qcom/pmic_glink.h | 32 +++ 4 files changed, 384 insertions(+) create mode 100644 drivers/soc/qcom/pmic_glink.c create mode 100644 include/linux/soc/qcom/pmic_glink.h diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 21c4ce2315ba..3f2b51905e23 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -100,6 +100,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 diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 3b92c6c8e0ec..29cccac472f3 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -9,6 +9,7 @@ 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_QMI_HELPERS) += qmi_helpers.o qmi_helpers-y += qmi_encdec.o qmi_interface.o obj-$(CONFIG_QCOM_RAMP_CTRL) += ramp_controller.o diff --git a/drivers/soc/qcom/pmic_glink.c b/drivers/soc/qcom/pmic_glink.c new file mode 100644 index 000000000000..bb3fb57abcc6 --- /dev/null +++ b/drivers/soc/qcom/pmic_glink.c @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2019-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2022, Linaro Ltd + */ +#include +#include +#include +#include +#include +#include +#include + +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"); diff --git a/include/linux/soc/qcom/pmic_glink.h b/include/linux/soc/qcom/pmic_glink.h new file mode 100644 index 000000000000..fd124aa18c81 --- /dev/null +++ b/include/linux/soc/qcom/pmic_glink.h @@ -0,0 +1,32 @@ +/* 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 From patchwork Mon Jan 30 04:20:02 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Bjorn Andersson X-Patchwork-Id: 648736 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id A2D41C64EC3 for ; Mon, 30 Jan 2023 04:20:20 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S235456AbjA3EUT (ORCPT ); Sun, 29 Jan 2023 23:20:19 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:33784 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S234878AbjA3EUQ (ORCPT ); Sun, 29 Jan 2023 23:20:16 -0500 Received: from mx0a-0031df01.pphosted.com (mx0a-0031df01.pphosted.com [205.220.168.131]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id CC2601F5C3; Sun, 29 Jan 2023 20:20:13 -0800 (PST) Received: from pps.filterd (m0279866.ppops.net [127.0.0.1]) by mx0a-0031df01.pphosted.com (8.17.1.19/8.17.1.19) with ESMTP id 30U3v0cQ031534; Mon, 30 Jan 2023 04:20:10 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=quicinc.com; h=from : to : cc : subject : date : message-id : in-reply-to : references : mime-version : content-transfer-encoding : content-type; s=qcppdkim1; bh=uNDvwp0KyUJrILavw3WSJjSfME8W4Bq17M5E4TOEmcI=; b=W44I4+JVpGfO9XsKo+pQxX6y6D1kN2T9wrzk7ycjlPi8GgQmWV9wWqMlIvuI1hXYbwN1 NtXZTj3G8WwACp2xFdCdBsAJIDZxa3Dk/QTZf6j+p2De2lhdxSPvZ2H8VjciyqDlGZS8 pQZ9pns6oC3ET3uzBPt1bDrzCA0AfmArlLx3ZMyvYBXQAM5HHyAsRp9xNXrkWbQ6j7ZK Sg8bgrgykm8TribkqZR7ad8hIVMGsrCLF0ZFlVy7bsI0OW9YE+wwO01tB3AoDR0cK98l 8O++3MqnDfQY3sCoksfBQhEig90rE91ygVxUa1tvMKFwOT/pu6EnzgQJmr8AvvxRryow VA== Received: from nalasppmta02.qualcomm.com (Global_NAT1.qualcomm.com [129.46.96.20]) by mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 3ncw0g2kx4-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Mon, 30 Jan 2023 04:20:10 +0000 Received: from nalasex01c.na.qualcomm.com (nalasex01c.na.qualcomm.com [10.47.97.35]) by NALASPPMTA02.qualcomm.com (8.17.1.5/8.17.1.5) with ESMTPS id 30U4K9sW007179 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Mon, 30 Jan 2023 04:20:09 GMT Received: from hu-bjorande-lv.qualcomm.com (10.49.16.6) by nalasex01c.na.qualcomm.com (10.47.97.35) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.986.36; Sun, 29 Jan 2023 20:20:09 -0800 From: Bjorn Andersson To: Andy Gross , Bjorn Andersson , Konrad Dybcio , Rob Herring , Krzysztof Kozlowski , Sebastian Reichel CC: , , , , "Subbaraman Narayanamurthy" , Johan Hovold , Neil Armstrong Subject: [PATCH v3 3/4] soc: qcom: pmic_glink: Introduce altmode support Date: Sun, 29 Jan 2023 20:20:02 -0800 Message-ID: <20230130042003.577063-4-quic_bjorande@quicinc.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20230130042003.577063-1-quic_bjorande@quicinc.com> References: <20230130042003.577063-1-quic_bjorande@quicinc.com> MIME-Version: 1.0 X-Originating-IP: [10.49.16.6] X-ClientProxiedBy: nalasex01b.na.qualcomm.com (10.47.209.197) To nalasex01c.na.qualcomm.com (10.47.97.35) X-QCInternal: smtphost X-Proofpoint-Virus-Version: vendor=nai engine=6200 definitions=5800 signatures=585085 X-Proofpoint-GUID: dYX8Z-wsPeKW2Wr8Xt2HEvkNHBCBLMuB X-Proofpoint-ORIG-GUID: dYX8Z-wsPeKW2Wr8Xt2HEvkNHBCBLMuB X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.219,Aquarius:18.0.930,Hydra:6.0.562,FMLib:17.11.122.1 definitions=2023-01-30_02,2023-01-27_01,2022-06-22_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 suspectscore=0 mlxscore=0 bulkscore=0 clxscore=1015 impostorscore=0 mlxlogscore=999 priorityscore=1501 spamscore=0 adultscore=0 lowpriorityscore=0 phishscore=0 malwarescore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.12.0-2212070000 definitions=main-2301300040 Precedence: bulk List-ID: X-Mailing-List: linux-arm-msm@vger.kernel.org From: Bjorn Andersson With the PMIC GLINK service, the host OS subscribes to USB-C altmode messages, which are sent by the firmware to notify the host OS about state updates and HPD interrupts. The pmic_glink_altmode driver registers for these notifications and propagates the notifications as typec_mux, typec_switch and DRM OOB notifications as necessary to implement DisplayPort altmode support. Signed-off-by: Bjorn Andersson Acked-by: Neil Armstrong Tested-by: Konrad Dybcio # SM8350 PDX215 Signed-off-by: Bjorn Andersson --- Changes since v2: - Include bitfield.h drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/pmic_glink_altmode.c | 478 ++++++++++++++++++++++++++ 2 files changed, 479 insertions(+) create mode 100644 drivers/soc/qcom/pmic_glink_altmode.c diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 29cccac472f3..f30552bf4da7 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -10,6 +10,7 @@ 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 diff --git a/drivers/soc/qcom/pmic_glink_altmode.c b/drivers/soc/qcom/pmic_glink_altmode.c new file mode 100644 index 000000000000..71f861b8cb51 --- /dev/null +++ b/drivers/soc/qcom/pmic_glink_altmode.c @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2019-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2022, Linaro Ltd + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#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"); From patchwork Mon Jan 30 04:20:03 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Bjorn Andersson X-Patchwork-Id: 649006 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 59EBCC636D3 for ; Mon, 30 Jan 2023 04:20:23 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S235564AbjA3EUV (ORCPT ); Sun, 29 Jan 2023 23:20:21 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:33870 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S235501AbjA3EUU (ORCPT ); Sun, 29 Jan 2023 23:20:20 -0500 Received: from mx0a-0031df01.pphosted.com (mx0a-0031df01.pphosted.com [205.220.168.131]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 619D11E1E1; Sun, 29 Jan 2023 20:20:15 -0800 (PST) Received: from pps.filterd (m0279864.ppops.net [127.0.0.1]) by mx0a-0031df01.pphosted.com (8.17.1.19/8.17.1.19) with ESMTP id 30U3xok5027892; Mon, 30 Jan 2023 04:20:11 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=quicinc.com; h=from : to : cc : subject : date : message-id : in-reply-to : references : mime-version : content-transfer-encoding : content-type; s=qcppdkim1; bh=sahwGwf2IK0RVtxfNQe8g2FGKrcet/FRjqB2RiV7Imw=; b=W6BBuFutfhkadmyYoZIq65EfRjRRdDV9rQh3ZvKsOlE1u30e92Wc0WX/b5p13bo/Dare z7M4Fv2VnJYmC9wZ+8/001kAPbdyYEljcGfUMgXb9wupIGPTdvnHAJ9u56RyIPxValJy BKWlB/QMLlBoye2OgzUEG205KeOgHfpwdJflq7aEVROLoI6CHtMt0hzWGRjUa1RLgjOu Ug8ksqkAwAWKQO7W52ZrrNOge90pHfFxCnsKReKsU5VXbifmm1+kmPSs7iyO75letm8I OSrF9TZxLNlYdXkCw7zc/OL5HnMmFoDdixrvAz/boPYcFObZMxUG6I140Zeql8e1axFb Mw== Received: from nalasppmta04.qualcomm.com (Global_NAT1.qualcomm.com [129.46.96.20]) by mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 3ncvvu2mft-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Mon, 30 Jan 2023 04:20:10 +0000 Received: from nalasex01c.na.qualcomm.com (nalasex01c.na.qualcomm.com [10.47.97.35]) by NALASPPMTA04.qualcomm.com (8.17.1.5/8.17.1.5) with ESMTPS id 30U4K9mh004637 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Mon, 30 Jan 2023 04:20:10 GMT Received: from hu-bjorande-lv.qualcomm.com (10.49.16.6) by nalasex01c.na.qualcomm.com (10.47.97.35) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.986.36; Sun, 29 Jan 2023 20:20:09 -0800 From: Bjorn Andersson To: Andy Gross , Bjorn Andersson , Konrad Dybcio , Rob Herring , Krzysztof Kozlowski , Sebastian Reichel CC: , , , , "Subbaraman Narayanamurthy" , Johan Hovold , Neil Armstrong Subject: [PATCH v3 4/4] power: supply: Introduce Qualcomm PMIC GLINK power supply Date: Sun, 29 Jan 2023 20:20:03 -0800 Message-ID: <20230130042003.577063-5-quic_bjorande@quicinc.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20230130042003.577063-1-quic_bjorande@quicinc.com> References: <20230130042003.577063-1-quic_bjorande@quicinc.com> MIME-Version: 1.0 X-Originating-IP: [10.49.16.6] X-ClientProxiedBy: nalasex01b.na.qualcomm.com (10.47.209.197) To nalasex01c.na.qualcomm.com (10.47.97.35) X-QCInternal: smtphost X-Proofpoint-Virus-Version: vendor=nai engine=6200 definitions=5800 signatures=585085 X-Proofpoint-GUID: Siem8cyLYomoJWul3tzW_9O1_KfCMW63 X-Proofpoint-ORIG-GUID: Siem8cyLYomoJWul3tzW_9O1_KfCMW63 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.219,Aquarius:18.0.930,Hydra:6.0.562,FMLib:17.11.122.1 definitions=2023-01-30_02,2023-01-27_01,2022-06-22_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 malwarescore=0 mlxlogscore=999 suspectscore=0 spamscore=0 mlxscore=0 phishscore=0 clxscore=1015 lowpriorityscore=0 priorityscore=1501 bulkscore=0 adultscore=0 impostorscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.12.0-2212070000 definitions=main-2301300040 Precedence: bulk List-ID: X-Mailing-List: linux-arm-msm@vger.kernel.org From: Bjorn Andersson The PMIC GLINK service, running on a coprocessor of modern Qualcomm platforms, deals with battery charging and fuel gauging, as well as reporting status of AC and wireless power supplies. As this is just one of the functionalities provided by the PMIC GLINK service, this power supply driver is implemented as an auxilirary bus driver, spawned by the main "pmic glink" driver when the PMIC GLINK service is detected. Signed-off-by: Bjorn Andersson Tested-by: Konrad Dybcio # SM8350 PDX215 Signed-off-by: Bjorn Andersson --- Changes since v2: - None drivers/power/supply/Kconfig | 9 + drivers/power/supply/Makefile | 1 + drivers/power/supply/qcom_battmgr.c | 1421 +++++++++++++++++++++++++++ 3 files changed, 1431 insertions(+) create mode 100644 drivers/power/supply/qcom_battmgr.c diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index e2f8dfcdd2a9..a2292f19d301 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -174,6 +174,15 @@ config BATTERY_PMU Say Y here to expose battery information on Apple machines through the generic battery class. +config BATTERY_QCOM_BATTMGR + tristate "Qualcomm PMIC GLINK battery manager support" + depends on QCOM_PMIC_GLINK + select AUXILIARY_BUS + help + Say Y here to enable the Qualcomm PMIC GLINK power supply driver, + which is used on modern Qualcomm platforms to provide battery and + power supply information. + config BATTERY_OLPC tristate "One Laptop Per Child battery" depends on OLPC_EC diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 8cb3c7f5c111..dc4a3a58a5c0 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -33,6 +33,7 @@ obj-$(CONFIG_BATTERY_GAUGE_LTC2941) += ltc2941-battery-gauge.o obj-$(CONFIG_BATTERY_GOLDFISH) += goldfish_battery.o obj-$(CONFIG_BATTERY_LEGO_EV3) += lego_ev3_battery.o obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o +obj-$(CONFIG_BATTERY_QCOM_BATTMGR) += qcom_battmgr.o obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o obj-$(CONFIG_BATTERY_SAMSUNG_SDI) += samsung-sdi-battery.o obj-$(CONFIG_BATTERY_COLLIE) += collie_battery.o diff --git a/drivers/power/supply/qcom_battmgr.c b/drivers/power/supply/qcom_battmgr.c new file mode 100644 index 000000000000..ed169b47c538 --- /dev/null +++ b/drivers/power/supply/qcom_battmgr.c @@ -0,0 +1,1421 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2019-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2022, Linaro Ltd + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BATTMGR_CHEMISTRY_LEN 4 +#define BATTMGR_STRING_LEN 128 + +enum qcom_battmgr_variant { + QCOM_BATTMGR_SM8350, + QCOM_BATTMGR_SC8280XP, +}; + +#define BATTMGR_BAT_STATUS 0x1 + +#define BATTMGR_REQUEST_NOTIFICATION 0x4 + +#define BATTMGR_NOTIFICATION 0x7 +#define NOTIF_BAT_PROPERTY 0x30 +#define NOTIF_USB_PROPERTY 0x32 +#define NOTIF_WLS_PROPERTY 0x34 +#define NOTIF_BAT_INFO 0x81 +#define NOTIF_BAT_STATUS 0x80 + +#define BATTMGR_BAT_INFO 0x9 + +#define BATTMGR_BAT_DISCHARGE_TIME 0xc + +#define BATTMGR_BAT_CHARGE_TIME 0xd + +#define BATTMGR_BAT_PROPERTY_GET 0x30 +#define BATTMGR_BAT_PROPERTY_SET 0x31 +#define BATT_STATUS 0 +#define BATT_HEALTH 1 +#define BATT_PRESENT 2 +#define BATT_CHG_TYPE 3 +#define BATT_CAPACITY 4 +#define BATT_SOH 5 +#define BATT_VOLT_OCV 6 +#define BATT_VOLT_NOW 7 +#define BATT_VOLT_MAX 8 +#define BATT_CURR_NOW 9 +#define BATT_CHG_CTRL_LIM 10 +#define BATT_CHG_CTRL_LIM_MAX 11 +#define BATT_TEMP 12 +#define BATT_TECHNOLOGY 13 +#define BATT_CHG_COUNTER 14 +#define BATT_CYCLE_COUNT 15 +#define BATT_CHG_FULL_DESIGN 16 +#define BATT_CHG_FULL 17 +#define BATT_MODEL_NAME 18 +#define BATT_TTF_AVG 19 +#define BATT_TTE_AVG 20 +#define BATT_RESISTANCE 21 +#define BATT_POWER_NOW 22 +#define BATT_POWER_AVG 23 + +#define BATTMGR_USB_PROPERTY_GET 0x32 +#define BATTMGR_USB_PROPERTY_SET 0x33 +#define USB_ONLINE 0 +#define USB_VOLT_NOW 1 +#define USB_VOLT_MAX 2 +#define USB_CURR_NOW 3 +#define USB_CURR_MAX 4 +#define USB_INPUT_CURR_LIMIT 5 +#define USB_TYPE 6 +#define USB_ADAP_TYPE 7 +#define USB_MOISTURE_DET_EN 8 +#define USB_MOISTURE_DET_STS 9 + +#define BATTMGR_WLS_PROPERTY_GET 0x34 +#define BATTMGR_WLS_PROPERTY_SET 0x35 +#define WLS_ONLINE 0 +#define WLS_VOLT_NOW 1 +#define WLS_VOLT_MAX 2 +#define WLS_CURR_NOW 3 +#define WLS_CURR_MAX 4 +#define WLS_TYPE 5 +#define WLS_BOOST_EN 6 + +struct qcom_battmgr_enable_request { + struct pmic_glink_hdr hdr; + __le32 battery_id; + __le32 power_state; + __le32 low_capacity; + __le32 high_capacity; +}; + +struct qcom_battmgr_property_request { + struct pmic_glink_hdr hdr; + __le32 battery; + __le32 property; + __le32 value; +}; + +struct qcom_battmgr_update_request { + struct pmic_glink_hdr hdr; + u32 battery_id; +}; + +struct qcom_battmgr_charge_time_request { + struct pmic_glink_hdr hdr; + __le32 battery_id; + __le32 percent; + __le32 reserved; +}; + +struct qcom_battmgr_discharge_time_request { + struct pmic_glink_hdr hdr; + __le32 battery_id; + __le32 rate; /* 0 for current rate */ + __le32 reserved; +}; + +struct qcom_battmgr_message { + struct pmic_glink_hdr hdr; + union { + struct { + __le32 property; + __le32 value; + __le32 result; + } intval; + struct { + __le32 property; + char model[BATTMGR_STRING_LEN]; + } strval; + struct { + /* + * 0: mWh + * 1: mAh + */ + __le32 power_unit; + __le32 design_capacity; + __le32 last_full_capacity; + /* + * 0 nonrechargable + * 1 rechargable + */ + __le32 battery_tech; + __le32 design_voltage; /* mV */ + __le32 capacity_low; + __le32 capacity_warning; + __le32 cycle_count; + /* thousandth of persent */ + __le32 accuracy; + __le32 max_sample_time_ms; + __le32 min_sample_time_ms; + __le32 max_average_interval_ms; + __le32 min_average_interval_ms; + /* granularity between low and warning */ + __le32 capacity_granularity1; + /* granularity between warning and full */ + __le32 capacity_granularity2; + /* + * 0: no + * 1: cold + * 2: hot + */ + __le32 swappable; + __le32 capabilities; + char model_number[BATTMGR_STRING_LEN]; + char serial_number[BATTMGR_STRING_LEN]; + char battery_type[BATTMGR_STRING_LEN]; + char oem_info[BATTMGR_STRING_LEN]; + char battery_chemistry[BATTMGR_CHEMISTRY_LEN]; + char uid[BATTMGR_STRING_LEN]; + __le32 critical_bias; + u8 day; + u8 month; + __le16 year; + __le32 battery_id; + } info; + struct { + /* + * BIT(0) discharging + * BIT(1) charging + * BIT(2) critical low + */ + __le32 battery_state; + /* mWh or mAh, based on info->power_unit */ + __le32 capacity; + __le32 rate; + /* mv */ + __le32 battery_voltage; + /* + * BIT(0) power online + * BIT(1) discharging + * BIT(2) charging + * BIT(3) battery critical + */ + __le32 power_state; + /* + * 1: AC + * 2: USB + * 3: Wireless + */ + __le32 charging_source; + __le32 temperature; + } status; + __le32 time; + __le32 notification; + }; +}; + +#define BATTMGR_CHARGING_SOURCE_AC 1 +#define BATTMGR_CHARGING_SOURCE_USB 2 +#define BATTMGR_CHARGING_SOURCE_WIRELESS 3 + +enum qcom_battmgr_unit { + QCOM_BATTMGR_UNIT_mWh = 0, + QCOM_BATTMGR_UNIT_mAh = 1 +}; + +struct qcom_battmgr_info { + bool valid; + + bool present; + unsigned int charge_type; + unsigned int design_capacity; + unsigned int last_full_capacity; + unsigned int voltage_max_design; + unsigned int voltage_max; + unsigned int capacity_low; + unsigned int capacity_warning; + unsigned int cycle_count; + unsigned int charge_count; + char model_number[BATTMGR_STRING_LEN]; + char serial_number[BATTMGR_STRING_LEN]; + char oem_info[BATTMGR_STRING_LEN]; + unsigned char technology; + unsigned char day; + unsigned char month; + unsigned short year; +}; + +struct qcom_battmgr_status { + unsigned int status; + unsigned int health; + unsigned int capacity; + unsigned int percent; + int current_now; + int power_now; + unsigned int voltage_now; + unsigned int voltage_ocv; + unsigned int temperature; + + unsigned int discharge_time; + unsigned int charge_time; +}; + +struct qcom_battmgr_ac { + bool online; +}; + +struct qcom_battmgr_usb { + bool online; + unsigned int voltage_now; + unsigned int voltage_max; + unsigned int current_now; + unsigned int current_max; + unsigned int current_limit; + unsigned int usb_type; +}; + +struct qcom_battmgr_wireless { + bool online; + unsigned int voltage_now; + unsigned int voltage_max; + unsigned int current_now; + unsigned int current_max; +}; + +struct qcom_battmgr { + struct device *dev; + struct pmic_glink_client *client; + + enum qcom_battmgr_variant variant; + + struct power_supply *ac_psy; + struct power_supply *bat_psy; + struct power_supply *usb_psy; + struct power_supply *wls_psy; + + enum qcom_battmgr_unit unit; + + int error; + struct completion ack; + + bool service_up; + + struct qcom_battmgr_info info; + struct qcom_battmgr_status status; + struct qcom_battmgr_ac ac; + struct qcom_battmgr_usb usb; + struct qcom_battmgr_wireless wireless; + + struct work_struct enable_work; + + /* + * @lock is used to prevent concurrent power supply requests to the + * firmware, as it then stops responding. + */ + struct mutex lock; +}; + +static int qcom_battmgr_request(struct qcom_battmgr *battmgr, void *data, size_t len) +{ + unsigned long left; + int ret; + + reinit_completion(&battmgr->ack); + + battmgr->error = 0; + + ret = pmic_glink_send(battmgr->client, data, len); + if (ret < 0) + return ret; + + left = wait_for_completion_timeout(&battmgr->ack, HZ); + if (!left) + return -ETIMEDOUT; + + return battmgr->error; +} + +static int qcom_battmgr_request_property(struct qcom_battmgr *battmgr, int opcode, + int property, u32 value) +{ + struct qcom_battmgr_property_request request = { + .hdr.owner = cpu_to_le32(PMIC_GLINK_OWNER_BATTMGR), + .hdr.type = cpu_to_le32(PMIC_GLINK_REQ_RESP), + .hdr.opcode = cpu_to_le32(opcode), + .battery = cpu_to_le32(0), + .property = cpu_to_le32(property), + .value = cpu_to_le32(value), + }; + + return qcom_battmgr_request(battmgr, &request, sizeof(request)); +} + +static int qcom_battmgr_update_status(struct qcom_battmgr *battmgr) +{ + struct qcom_battmgr_update_request request = { + .hdr.owner = cpu_to_le32(PMIC_GLINK_OWNER_BATTMGR), + .hdr.type = cpu_to_le32(PMIC_GLINK_REQ_RESP), + .hdr.opcode = cpu_to_le32(BATTMGR_BAT_STATUS), + .battery_id = cpu_to_le32(0), + }; + + return qcom_battmgr_request(battmgr, &request, sizeof(request)); +} + +static int qcom_battmgr_update_info(struct qcom_battmgr *battmgr) +{ + struct qcom_battmgr_update_request request = { + .hdr.owner = cpu_to_le32(PMIC_GLINK_OWNER_BATTMGR), + .hdr.type = cpu_to_le32(PMIC_GLINK_REQ_RESP), + .hdr.opcode = cpu_to_le32(BATTMGR_BAT_INFO), + .battery_id = cpu_to_le32(0), + }; + + return qcom_battmgr_request(battmgr, &request, sizeof(request)); +} + +static int qcom_battmgr_update_charge_time(struct qcom_battmgr *battmgr) +{ + struct qcom_battmgr_charge_time_request request = { + .hdr.owner = cpu_to_le32(PMIC_GLINK_OWNER_BATTMGR), + .hdr.type = cpu_to_le32(PMIC_GLINK_REQ_RESP), + .hdr.opcode = cpu_to_le32(BATTMGR_BAT_CHARGE_TIME), + .battery_id = cpu_to_le32(0), + .percent = cpu_to_le32(100), + }; + + return qcom_battmgr_request(battmgr, &request, sizeof(request)); +} + +static int qcom_battmgr_update_discharge_time(struct qcom_battmgr *battmgr) +{ + struct qcom_battmgr_discharge_time_request request = { + .hdr.owner = cpu_to_le32(PMIC_GLINK_OWNER_BATTMGR), + .hdr.type = cpu_to_le32(PMIC_GLINK_REQ_RESP), + .hdr.opcode = cpu_to_le32(BATTMGR_BAT_DISCHARGE_TIME), + .battery_id = cpu_to_le32(0), + .rate = cpu_to_le32(0), + }; + + return qcom_battmgr_request(battmgr, &request, sizeof(request)); +} + +static const u8 sm8350_bat_prop_map[] = { + [POWER_SUPPLY_PROP_STATUS] = BATT_STATUS, + [POWER_SUPPLY_PROP_HEALTH] = BATT_HEALTH, + [POWER_SUPPLY_PROP_PRESENT] = BATT_PRESENT, + [POWER_SUPPLY_PROP_CHARGE_TYPE] = BATT_CHG_TYPE, + [POWER_SUPPLY_PROP_CAPACITY] = BATT_CAPACITY, + [POWER_SUPPLY_PROP_VOLTAGE_OCV] = BATT_VOLT_OCV, + [POWER_SUPPLY_PROP_VOLTAGE_NOW] = BATT_VOLT_NOW, + [POWER_SUPPLY_PROP_VOLTAGE_MAX] = BATT_VOLT_MAX, + [POWER_SUPPLY_PROP_CURRENT_NOW] = BATT_CURR_NOW, + [POWER_SUPPLY_PROP_TEMP] = BATT_TEMP, + [POWER_SUPPLY_PROP_TECHNOLOGY] = BATT_TECHNOLOGY, + [POWER_SUPPLY_PROP_CHARGE_COUNTER] = BATT_CHG_COUNTER, + [POWER_SUPPLY_PROP_CYCLE_COUNT] = BATT_CYCLE_COUNT, + [POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN] = BATT_CHG_FULL_DESIGN, + [POWER_SUPPLY_PROP_CHARGE_FULL] = BATT_CHG_FULL, + [POWER_SUPPLY_PROP_MODEL_NAME] = BATT_MODEL_NAME, + [POWER_SUPPLY_PROP_TIME_TO_FULL_AVG] = BATT_TTF_AVG, + [POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG] = BATT_TTE_AVG, + [POWER_SUPPLY_PROP_POWER_NOW] = BATT_POWER_NOW, +}; + +static int qcom_battmgr_bat_sm8350_update(struct qcom_battmgr *battmgr, + enum power_supply_property psp) +{ + unsigned int prop; + int ret; + + if (psp >= ARRAY_SIZE(sm8350_bat_prop_map)) + return -EINVAL; + + prop = sm8350_bat_prop_map[psp]; + + mutex_lock(&battmgr->lock); + ret = qcom_battmgr_request_property(battmgr, BATTMGR_BAT_PROPERTY_GET, prop, 0); + mutex_unlock(&battmgr->lock); + + return ret; +} + +static int qcom_battmgr_bat_sc8280xp_update(struct qcom_battmgr *battmgr, + enum power_supply_property psp) +{ + int ret; + + mutex_lock(&battmgr->lock); + + if (!battmgr->info.valid) { + ret = qcom_battmgr_update_info(battmgr); + if (ret < 0) + goto out_unlock; + battmgr->info.valid = true; + } + + ret = qcom_battmgr_update_status(battmgr); + if (ret < 0) + goto out_unlock; + + if (psp == POWER_SUPPLY_PROP_TIME_TO_FULL_AVG) { + ret = qcom_battmgr_update_charge_time(battmgr); + if (ret < 0) { + ret = -ENODATA; + goto out_unlock; + } + } + + if (psp == POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG) { + ret = qcom_battmgr_update_discharge_time(battmgr); + if (ret < 0) { + ret = -ENODATA; + goto out_unlock; + } + } + +out_unlock: + mutex_unlock(&battmgr->lock); + return ret; +} + +static int qcom_battmgr_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct qcom_battmgr *battmgr = power_supply_get_drvdata(psy); + enum qcom_battmgr_unit unit = battmgr->unit; + int ret; + + if (!battmgr->service_up) + return -ENODEV; + + if (battmgr->variant == QCOM_BATTMGR_SC8280XP) + ret = qcom_battmgr_bat_sc8280xp_update(battmgr, psp); + else + ret = qcom_battmgr_bat_sm8350_update(battmgr, psp); + if (ret < 0) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = battmgr->status.status; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = battmgr->info.charge_type; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = battmgr->status.health; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = battmgr->info.present; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = battmgr->info.technology; + break; + case POWER_SUPPLY_PROP_CYCLE_COUNT: + val->intval = battmgr->info.cycle_count; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = battmgr->info.voltage_max_design; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + val->intval = battmgr->info.voltage_max; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = battmgr->status.voltage_now; + break; + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + val->intval = battmgr->status.voltage_ocv; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = battmgr->status.current_now; + break; + case POWER_SUPPLY_PROP_POWER_NOW: + val->intval = battmgr->status.power_now; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + if (unit != QCOM_BATTMGR_UNIT_mAh) + return -ENODATA; + val->intval = battmgr->info.design_capacity; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + if (unit != QCOM_BATTMGR_UNIT_mAh) + return -ENODATA; + val->intval = battmgr->info.last_full_capacity; + break; + case POWER_SUPPLY_PROP_CHARGE_EMPTY: + if (unit != QCOM_BATTMGR_UNIT_mAh) + return -ENODATA; + val->intval = battmgr->info.capacity_low; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + if (unit != QCOM_BATTMGR_UNIT_mAh) + return -ENODATA; + val->intval = battmgr->status.capacity; + break; + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + val->intval = battmgr->info.charge_count; + break; + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + if (unit != QCOM_BATTMGR_UNIT_mWh) + return -ENODATA; + val->intval = battmgr->info.design_capacity; + break; + case POWER_SUPPLY_PROP_ENERGY_FULL: + if (unit != QCOM_BATTMGR_UNIT_mWh) + return -ENODATA; + val->intval = battmgr->info.last_full_capacity; + break; + case POWER_SUPPLY_PROP_ENERGY_EMPTY: + if (unit != QCOM_BATTMGR_UNIT_mWh) + return -ENODATA; + val->intval = battmgr->info.capacity_low; + break; + case POWER_SUPPLY_PROP_ENERGY_NOW: + if (unit != QCOM_BATTMGR_UNIT_mWh) + return -ENODATA; + val->intval = battmgr->status.capacity; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = battmgr->status.percent; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = battmgr->status.temperature; + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: + val->intval = battmgr->status.discharge_time; + break; + case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: + val->intval = battmgr->status.charge_time; + break; + case POWER_SUPPLY_PROP_MANUFACTURE_YEAR: + val->intval = battmgr->info.year; + break; + case POWER_SUPPLY_PROP_MANUFACTURE_MONTH: + val->intval = battmgr->info.month; + break; + case POWER_SUPPLY_PROP_MANUFACTURE_DAY: + val->intval = battmgr->info.day; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = battmgr->info.model_number; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = battmgr->info.oem_info; + break; + case POWER_SUPPLY_PROP_SERIAL_NUMBER: + val->strval = battmgr->info.serial_number; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const enum power_supply_property sc8280xp_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_POWER_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_EMPTY, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, + POWER_SUPPLY_PROP_ENERGY_FULL, + POWER_SUPPLY_PROP_ENERGY_EMPTY, + POWER_SUPPLY_PROP_ENERGY_NOW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_MANUFACTURE_YEAR, + POWER_SUPPLY_PROP_MANUFACTURE_MONTH, + POWER_SUPPLY_PROP_MANUFACTURE_DAY, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SERIAL_NUMBER, +}; + +static const struct power_supply_desc sc8280xp_bat_psy_desc = { + .name = "qcom-battmgr-bat", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = sc8280xp_bat_props, + .num_properties = ARRAY_SIZE(sc8280xp_bat_props), + .get_property = qcom_battmgr_bat_get_property, +}; + +static const enum power_supply_property sm8350_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_VOLTAGE_OCV, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, + POWER_SUPPLY_PROP_POWER_NOW, +}; + +static const struct power_supply_desc sm8350_bat_psy_desc = { + .name = "qcom-battmgr-bat", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = sm8350_bat_props, + .num_properties = ARRAY_SIZE(sm8350_bat_props), + .get_property = qcom_battmgr_bat_get_property, +}; + +static int qcom_battmgr_ac_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct qcom_battmgr *battmgr = power_supply_get_drvdata(psy); + int ret; + + if (!battmgr->service_up) + return -ENODEV; + + ret = qcom_battmgr_bat_sc8280xp_update(battmgr, psp); + if (ret) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = battmgr->ac.online; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const enum power_supply_property sc8280xp_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static const struct power_supply_desc sc8280xp_ac_psy_desc = { + .name = "qcom-battmgr-ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = sc8280xp_ac_props, + .num_properties = ARRAY_SIZE(sc8280xp_ac_props), + .get_property = qcom_battmgr_ac_get_property, +}; + +static const u8 sm8350_usb_prop_map[] = { + [POWER_SUPPLY_PROP_ONLINE] = USB_ONLINE, + [POWER_SUPPLY_PROP_VOLTAGE_NOW] = USB_VOLT_NOW, + [POWER_SUPPLY_PROP_VOLTAGE_MAX] = USB_VOLT_MAX, + [POWER_SUPPLY_PROP_CURRENT_NOW] = USB_CURR_NOW, + [POWER_SUPPLY_PROP_CURRENT_MAX] = USB_CURR_MAX, + [POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT] = USB_INPUT_CURR_LIMIT, + [POWER_SUPPLY_PROP_USB_TYPE] = USB_TYPE, +}; + +static int qcom_battmgr_usb_sm8350_update(struct qcom_battmgr *battmgr, + enum power_supply_property psp) +{ + unsigned int prop; + int ret; + + if (psp >= ARRAY_SIZE(sm8350_usb_prop_map)) + return -EINVAL; + + prop = sm8350_usb_prop_map[psp]; + + mutex_lock(&battmgr->lock); + ret = qcom_battmgr_request_property(battmgr, BATTMGR_USB_PROPERTY_GET, prop, 0); + mutex_unlock(&battmgr->lock); + + return ret; +} + +static int qcom_battmgr_usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct qcom_battmgr *battmgr = power_supply_get_drvdata(psy); + int ret; + + if (!battmgr->service_up) + return -ENODEV; + + if (battmgr->variant == QCOM_BATTMGR_SC8280XP) + ret = qcom_battmgr_bat_sc8280xp_update(battmgr, psp); + else + ret = qcom_battmgr_usb_sm8350_update(battmgr, psp); + if (ret) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = battmgr->usb.online; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = battmgr->usb.voltage_now; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + val->intval = battmgr->usb.voltage_max; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = battmgr->usb.current_now; + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = battmgr->usb.current_max; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + val->intval = battmgr->usb.current_limit; + break; + case POWER_SUPPLY_PROP_USB_TYPE: + val->intval = battmgr->usb.usb_type; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const enum power_supply_usb_type usb_psy_supported_types[] = { + POWER_SUPPLY_USB_TYPE_UNKNOWN, + POWER_SUPPLY_USB_TYPE_SDP, + POWER_SUPPLY_USB_TYPE_DCP, + POWER_SUPPLY_USB_TYPE_CDP, + POWER_SUPPLY_USB_TYPE_ACA, + POWER_SUPPLY_USB_TYPE_C, + POWER_SUPPLY_USB_TYPE_PD, + POWER_SUPPLY_USB_TYPE_PD_DRP, + POWER_SUPPLY_USB_TYPE_PD_PPS, + POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID, +}; + +static const enum power_supply_property sc8280xp_usb_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static const struct power_supply_desc sc8280xp_usb_psy_desc = { + .name = "qcom-battmgr-usb", + .type = POWER_SUPPLY_TYPE_USB, + .properties = sc8280xp_usb_props, + .num_properties = ARRAY_SIZE(sc8280xp_usb_props), + .get_property = qcom_battmgr_usb_get_property, + .usb_types = usb_psy_supported_types, + .num_usb_types = ARRAY_SIZE(usb_psy_supported_types), +}; + +static const enum power_supply_property sm8350_usb_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_USB_TYPE, +}; + +static const struct power_supply_desc sm8350_usb_psy_desc = { + .name = "qcom-battmgr-usb", + .type = POWER_SUPPLY_TYPE_USB, + .properties = sm8350_usb_props, + .num_properties = ARRAY_SIZE(sm8350_usb_props), + .get_property = qcom_battmgr_usb_get_property, + .usb_types = usb_psy_supported_types, + .num_usb_types = ARRAY_SIZE(usb_psy_supported_types), +}; + +static const u8 sm8350_wls_prop_map[] = { + [POWER_SUPPLY_PROP_ONLINE] = WLS_ONLINE, + [POWER_SUPPLY_PROP_VOLTAGE_NOW] = WLS_VOLT_NOW, + [POWER_SUPPLY_PROP_VOLTAGE_MAX] = WLS_VOLT_MAX, + [POWER_SUPPLY_PROP_CURRENT_NOW] = WLS_CURR_NOW, + [POWER_SUPPLY_PROP_CURRENT_MAX] = WLS_CURR_MAX, +}; + +static int qcom_battmgr_wls_sm8350_update(struct qcom_battmgr *battmgr, + enum power_supply_property psp) +{ + unsigned int prop; + int ret; + + if (psp >= ARRAY_SIZE(sm8350_wls_prop_map)) + return -EINVAL; + + prop = sm8350_wls_prop_map[psp]; + + mutex_lock(&battmgr->lock); + ret = qcom_battmgr_request_property(battmgr, BATTMGR_WLS_PROPERTY_GET, prop, 0); + mutex_unlock(&battmgr->lock); + + return ret; +} + +static int qcom_battmgr_wls_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct qcom_battmgr *battmgr = power_supply_get_drvdata(psy); + int ret; + + if (!battmgr->service_up) + return -ENODEV; + + if (battmgr->variant == QCOM_BATTMGR_SC8280XP) + ret = qcom_battmgr_bat_sc8280xp_update(battmgr, psp); + else + ret = qcom_battmgr_wls_sm8350_update(battmgr, psp); + if (ret < 0) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = battmgr->wireless.online; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = battmgr->wireless.voltage_now; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + val->intval = battmgr->wireless.voltage_max; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = battmgr->wireless.current_now; + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = battmgr->wireless.current_max; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const enum power_supply_property sc8280xp_wls_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static const struct power_supply_desc sc8280xp_wls_psy_desc = { + .name = "qcom-battmgr-wls", + .type = POWER_SUPPLY_TYPE_WIRELESS, + .properties = sc8280xp_wls_props, + .num_properties = ARRAY_SIZE(sc8280xp_wls_props), + .get_property = qcom_battmgr_wls_get_property, +}; + +static const enum power_supply_property sm8350_wls_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_MAX, +}; + +static const struct power_supply_desc sm8350_wls_psy_desc = { + .name = "qcom-battmgr-wls", + .type = POWER_SUPPLY_TYPE_WIRELESS, + .properties = sm8350_wls_props, + .num_properties = ARRAY_SIZE(sm8350_wls_props), + .get_property = qcom_battmgr_wls_get_property, +}; + +static void qcom_battmgr_notification(struct qcom_battmgr *battmgr, + const struct qcom_battmgr_message *msg, + int len) +{ + size_t payload_len = len - sizeof(struct pmic_glink_hdr); + unsigned int notification; + + if (payload_len != sizeof(msg->notification)) { + dev_warn(battmgr->dev, "ignoring notification with invalid length\n"); + return; + } + + notification = le32_to_cpu(msg->notification); + switch (notification) { + case NOTIF_BAT_INFO: + battmgr->info.valid = false; + fallthrough; + case NOTIF_BAT_STATUS: + case NOTIF_BAT_PROPERTY: + power_supply_changed(battmgr->bat_psy); + break; + case NOTIF_USB_PROPERTY: + power_supply_changed(battmgr->usb_psy); + break; + case NOTIF_WLS_PROPERTY: + power_supply_changed(battmgr->wls_psy); + break; + default: + dev_err(battmgr->dev, "unknown notification: %#x\n", notification); + break; + } +} + +static void qcom_battmgr_sc8280xp_strcpy(char *dest, const char *src) +{ + size_t len = src[0]; + + /* Some firmware versions return Pascal-style strings */ + if (len < BATTMGR_STRING_LEN && len == strnlen(src + 1, BATTMGR_STRING_LEN - 1)) { + memcpy(dest, src + 1, len); + dest[len] = '\0'; + } else { + memcpy(dest, src, BATTMGR_STRING_LEN); + } +} + +static unsigned int qcom_battmgr_sc8280xp_parse_technology(const char *chemistry) +{ + if (!strncmp(chemistry, "LIO", BATTMGR_CHEMISTRY_LEN)) + return POWER_SUPPLY_TECHNOLOGY_LION; + + pr_err("Unknown battery technology '%s'\n", chemistry); + return POWER_SUPPLY_TECHNOLOGY_UNKNOWN; +} + +static unsigned int qcom_battmgr_sc8280xp_convert_temp(unsigned int temperature) +{ + return DIV_ROUND_CLOSEST(temperature, 10); +} + +static void qcom_battmgr_sc8280xp_callback(struct qcom_battmgr *battmgr, + const struct qcom_battmgr_message *resp, + size_t len) +{ + unsigned int opcode = le32_to_cpu(resp->hdr.opcode); + unsigned int source; + unsigned int state; + size_t payload_len = len - sizeof(struct pmic_glink_hdr); + + if (payload_len < sizeof(__le32)) { + dev_warn(battmgr->dev, "invalid payload length for %#x: %zd\n", + opcode, len); + return; + } + + switch (opcode) { + case BATTMGR_REQUEST_NOTIFICATION: + battmgr->error = 0; + break; + case BATTMGR_BAT_INFO: + if (payload_len != sizeof(resp->info)) { + dev_warn(battmgr->dev, + "invalid payload length for battery information request: %zd\n", + payload_len); + battmgr->error = -ENODATA; + return; + } + + battmgr->unit = le32_to_cpu(resp->info.power_unit); + + battmgr->info.present = true; + battmgr->info.design_capacity = le32_to_cpu(resp->info.design_capacity) * 1000; + battmgr->info.last_full_capacity = le32_to_cpu(resp->info.last_full_capacity) * 1000; + battmgr->info.voltage_max_design = le32_to_cpu(resp->info.design_voltage) * 1000; + battmgr->info.capacity_low = le32_to_cpu(resp->info.capacity_low) * 1000; + battmgr->info.cycle_count = le32_to_cpu(resp->info.cycle_count); + qcom_battmgr_sc8280xp_strcpy(battmgr->info.model_number, resp->info.model_number); + qcom_battmgr_sc8280xp_strcpy(battmgr->info.serial_number, resp->info.serial_number); + battmgr->info.technology = qcom_battmgr_sc8280xp_parse_technology(resp->info.battery_chemistry); + qcom_battmgr_sc8280xp_strcpy(battmgr->info.oem_info, resp->info.oem_info); + battmgr->info.day = resp->info.day; + battmgr->info.month = resp->info.month; + battmgr->info.year = le16_to_cpu(resp->info.year); + break; + case BATTMGR_BAT_STATUS: + if (payload_len != sizeof(resp->status)) { + dev_warn(battmgr->dev, + "invalid payload length for battery status request: %zd\n", + payload_len); + battmgr->error = -ENODATA; + return; + } + + state = le32_to_cpu(resp->status.battery_state); + if (state & BIT(0)) + battmgr->status.status = POWER_SUPPLY_STATUS_DISCHARGING; + else if (state & BIT(1)) + battmgr->status.status = POWER_SUPPLY_STATUS_CHARGING; + else + battmgr->status.status = POWER_SUPPLY_STATUS_NOT_CHARGING; + + battmgr->status.capacity = le32_to_cpu(resp->status.capacity) * 1000; + battmgr->status.power_now = le32_to_cpu(resp->status.rate) * 1000; + battmgr->status.voltage_now = le32_to_cpu(resp->status.battery_voltage) * 1000; + battmgr->status.temperature = qcom_battmgr_sc8280xp_convert_temp(le32_to_cpu(resp->status.temperature)); + + source = le32_to_cpu(resp->status.charging_source); + battmgr->ac.online = source == BATTMGR_CHARGING_SOURCE_AC; + battmgr->usb.online = source == BATTMGR_CHARGING_SOURCE_USB; + battmgr->wireless.online = source == BATTMGR_CHARGING_SOURCE_WIRELESS; + break; + case BATTMGR_BAT_DISCHARGE_TIME: + battmgr->status.discharge_time = le32_to_cpu(resp->time); + break; + case BATTMGR_BAT_CHARGE_TIME: + battmgr->status.charge_time = le32_to_cpu(resp->time); + break; + default: + dev_warn(battmgr->dev, "unknown message %#x\n", opcode); + break; + } + + complete(&battmgr->ack); +} + +static void qcom_battmgr_sm8350_callback(struct qcom_battmgr *battmgr, + const struct qcom_battmgr_message *resp, + size_t len) +{ + unsigned int property; + unsigned int opcode = le32_to_cpu(resp->hdr.opcode); + size_t payload_len = len - sizeof(struct pmic_glink_hdr); + unsigned int val; + + if (payload_len < sizeof(__le32)) { + dev_warn(battmgr->dev, "invalid payload length for %#x: %zd\n", + opcode, len); + return; + } + + switch (opcode) { + case BATTMGR_BAT_PROPERTY_GET: + property = le32_to_cpu(resp->intval.property); + if (property == BATT_MODEL_NAME) { + if (payload_len != sizeof(resp->strval)) { + dev_warn(battmgr->dev, + "invalid payload length for BATT_MODEL_NAME request: %zd\n", + payload_len); + battmgr->error = -ENODATA; + return; + } + } else { + if (payload_len != sizeof(resp->intval)) { + dev_warn(battmgr->dev, + "invalid payload length for %#x request: %zd\n", + property, payload_len); + battmgr->error = -ENODATA; + return; + } + + battmgr->error = le32_to_cpu(resp->intval.result); + if (battmgr->error) + goto out_complete; + } + + switch (property) { + case BATT_STATUS: + battmgr->status.status = le32_to_cpu(resp->intval.value); + break; + case BATT_HEALTH: + battmgr->status.health = le32_to_cpu(resp->intval.value); + break; + case BATT_PRESENT: + battmgr->info.present = le32_to_cpu(resp->intval.value); + break; + case BATT_CHG_TYPE: + battmgr->info.charge_type = le32_to_cpu(resp->intval.value); + break; + case BATT_CAPACITY: + battmgr->status.percent = le32_to_cpu(resp->intval.value); + do_div(battmgr->status.percent, 100); + break; + case BATT_VOLT_OCV: + battmgr->status.voltage_ocv = le32_to_cpu(resp->intval.value); + break; + case BATT_VOLT_NOW: + battmgr->status.voltage_now = le32_to_cpu(resp->intval.value); + break; + case BATT_VOLT_MAX: + battmgr->info.voltage_max = le32_to_cpu(resp->intval.value); + break; + case BATT_CURR_NOW: + battmgr->status.current_now = le32_to_cpu(resp->intval.value); + break; + case BATT_TEMP: + val = le32_to_cpu(resp->intval.value); + battmgr->status.temperature = DIV_ROUND_CLOSEST(val, 10); + break; + case BATT_TECHNOLOGY: + battmgr->info.technology = le32_to_cpu(resp->intval.value); + break; + case BATT_CHG_COUNTER: + battmgr->info.charge_count = le32_to_cpu(resp->intval.value); + break; + case BATT_CYCLE_COUNT: + battmgr->info.cycle_count = le32_to_cpu(resp->intval.value); + break; + case BATT_CHG_FULL_DESIGN: + battmgr->info.design_capacity = le32_to_cpu(resp->intval.value); + break; + case BATT_CHG_FULL: + battmgr->info.last_full_capacity = le32_to_cpu(resp->intval.value); + break; + case BATT_MODEL_NAME: + strscpy(battmgr->info.model_number, resp->strval.model, BATTMGR_STRING_LEN); + break; + case BATT_TTF_AVG: + battmgr->status.charge_time = le32_to_cpu(resp->intval.value); + break; + case BATT_TTE_AVG: + battmgr->status.discharge_time = le32_to_cpu(resp->intval.value); + break; + case BATT_POWER_NOW: + battmgr->status.power_now = le32_to_cpu(resp->intval.value); + break; + default: + dev_warn(battmgr->dev, "unknown property %#x\n", property); + break; + } + break; + case BATTMGR_USB_PROPERTY_GET: + property = le32_to_cpu(resp->intval.property); + if (payload_len != sizeof(resp->intval)) { + dev_warn(battmgr->dev, + "invalid payload length for %#x request: %zd\n", + property, payload_len); + battmgr->error = -ENODATA; + return; + } + + battmgr->error = le32_to_cpu(resp->intval.result); + if (battmgr->error) + goto out_complete; + + switch (property) { + case USB_ONLINE: + battmgr->usb.online = le32_to_cpu(resp->intval.value); + break; + case USB_VOLT_NOW: + battmgr->usb.voltage_now = le32_to_cpu(resp->intval.value); + break; + case USB_VOLT_MAX: + battmgr->usb.voltage_max = le32_to_cpu(resp->intval.value); + break; + case USB_CURR_NOW: + battmgr->usb.current_now = le32_to_cpu(resp->intval.value); + break; + case USB_CURR_MAX: + battmgr->usb.current_max = le32_to_cpu(resp->intval.value); + break; + case USB_INPUT_CURR_LIMIT: + battmgr->usb.current_limit = le32_to_cpu(resp->intval.value); + break; + case USB_TYPE: + battmgr->usb.usb_type = le32_to_cpu(resp->intval.value); + break; + default: + dev_warn(battmgr->dev, "unknown property %#x\n", property); + break; + } + break; + case BATTMGR_WLS_PROPERTY_GET: + property = le32_to_cpu(resp->intval.property); + if (payload_len != sizeof(resp->intval)) { + dev_warn(battmgr->dev, + "invalid payload length for %#x request: %zd\n", + property, payload_len); + battmgr->error = -ENODATA; + return; + } + + battmgr->error = le32_to_cpu(resp->intval.result); + if (battmgr->error) + goto out_complete; + + switch (property) { + case WLS_ONLINE: + battmgr->wireless.online = le32_to_cpu(resp->intval.value); + break; + case WLS_VOLT_NOW: + battmgr->wireless.voltage_now = le32_to_cpu(resp->intval.value); + break; + case WLS_VOLT_MAX: + battmgr->wireless.voltage_max = le32_to_cpu(resp->intval.value); + break; + case WLS_CURR_NOW: + battmgr->wireless.current_now = le32_to_cpu(resp->intval.value); + break; + case WLS_CURR_MAX: + battmgr->wireless.current_max = le32_to_cpu(resp->intval.value); + break; + default: + dev_warn(battmgr->dev, "unknown property %#x\n", property); + break; + } + break; + case BATTMGR_REQUEST_NOTIFICATION: + battmgr->error = 0; + break; + default: + dev_warn(battmgr->dev, "unknown message %#x\n", opcode); + break; + } + +out_complete: + complete(&battmgr->ack); +} + +static void qcom_battmgr_callback(const void *data, size_t len, void *priv) +{ + const struct pmic_glink_hdr *hdr = data; + struct qcom_battmgr *battmgr = priv; + unsigned int opcode = le32_to_cpu(hdr->opcode); + + if (opcode == BATTMGR_NOTIFICATION) + qcom_battmgr_notification(battmgr, data, len); + else if (battmgr->variant == QCOM_BATTMGR_SC8280XP) + qcom_battmgr_sc8280xp_callback(battmgr, data, len); + else + qcom_battmgr_sm8350_callback(battmgr, data, len); +} + +static void qcom_battmgr_enable_worker(struct work_struct *work) +{ + struct qcom_battmgr *battmgr = container_of(work, struct qcom_battmgr, enable_work); + struct qcom_battmgr_enable_request req = { + .hdr.owner = PMIC_GLINK_OWNER_BATTMGR, + .hdr.type = PMIC_GLINK_NOTIFY, + .hdr.opcode = BATTMGR_REQUEST_NOTIFICATION, + }; + int ret; + + ret = qcom_battmgr_request(battmgr, &req, sizeof(req)); + if (ret) + dev_err(battmgr->dev, "failed to request power notifications\n"); +} + +static void qcom_battmgr_pdr_notify(void *priv, int state) +{ + struct qcom_battmgr *battmgr = priv; + + if (state == SERVREG_SERVICE_STATE_UP) { + battmgr->service_up = true; + schedule_work(&battmgr->enable_work); + } else { + battmgr->service_up = false; + } +} + +static const struct of_device_id qcom_battmgr_of_variants[] = { + { .compatible = "qcom,sc8180x-pmic-glink", .data = (void *)QCOM_BATTMGR_SC8280XP }, + { .compatible = "qcom,sc8280xp-pmic-glink", .data = (void *)QCOM_BATTMGR_SC8280XP }, + /* Unmatched devices falls back to QCOM_BATTMGR_SM8350 */ + {} +}; + +static char *qcom_battmgr_battery[] = { "battery" }; + +static int qcom_battmgr_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *id) +{ + struct power_supply_config psy_cfg_supply = {}; + struct power_supply_config psy_cfg = {}; + const struct of_device_id *match; + struct qcom_battmgr *battmgr; + struct device *dev = &adev->dev; + + battmgr = devm_kzalloc(dev, sizeof(*battmgr), GFP_KERNEL); + if (!battmgr) + return -ENOMEM; + + battmgr->dev = dev; + + psy_cfg.drv_data = battmgr; + psy_cfg.of_node = adev->dev.of_node; + + psy_cfg_supply.drv_data = battmgr; + psy_cfg_supply.of_node = adev->dev.of_node; + psy_cfg_supply.supplied_to = qcom_battmgr_battery; + psy_cfg_supply.num_supplicants = 1; + + INIT_WORK(&battmgr->enable_work, qcom_battmgr_enable_worker); + mutex_init(&battmgr->lock); + init_completion(&battmgr->ack); + + match = of_match_device(qcom_battmgr_of_variants, dev->parent); + if (match) + battmgr->variant = (unsigned long)match->data; + else + battmgr->variant = QCOM_BATTMGR_SM8350; + + if (battmgr->variant == QCOM_BATTMGR_SC8280XP) { + battmgr->bat_psy = devm_power_supply_register(dev, &sc8280xp_bat_psy_desc, &psy_cfg); + if (IS_ERR(battmgr->bat_psy)) + return dev_err_probe(dev, PTR_ERR(battmgr->bat_psy), + "failed to register battery power supply\n"); + + battmgr->ac_psy = devm_power_supply_register(dev, &sc8280xp_ac_psy_desc, &psy_cfg_supply); + if (IS_ERR(battmgr->ac_psy)) + return dev_err_probe(dev, PTR_ERR(battmgr->ac_psy), + "failed to register AC power supply\n"); + + battmgr->usb_psy = devm_power_supply_register(dev, &sc8280xp_usb_psy_desc, &psy_cfg_supply); + if (IS_ERR(battmgr->usb_psy)) + return dev_err_probe(dev, PTR_ERR(battmgr->usb_psy), + "failed to register USB power supply\n"); + + battmgr->wls_psy = devm_power_supply_register(dev, &sc8280xp_wls_psy_desc, &psy_cfg_supply); + if (IS_ERR(battmgr->wls_psy)) + return dev_err_probe(dev, PTR_ERR(battmgr->wls_psy), + "failed to register wireless charing power supply\n"); + } else { + battmgr->bat_psy = devm_power_supply_register(dev, &sm8350_bat_psy_desc, &psy_cfg); + if (IS_ERR(battmgr->bat_psy)) + return dev_err_probe(dev, PTR_ERR(battmgr->bat_psy), + "failed to register battery power supply\n"); + + battmgr->usb_psy = devm_power_supply_register(dev, &sm8350_usb_psy_desc, &psy_cfg_supply); + if (IS_ERR(battmgr->usb_psy)) + return dev_err_probe(dev, PTR_ERR(battmgr->usb_psy), + "failed to register USB power supply\n"); + + battmgr->wls_psy = devm_power_supply_register(dev, &sm8350_wls_psy_desc, &psy_cfg_supply); + if (IS_ERR(battmgr->wls_psy)) + return dev_err_probe(dev, PTR_ERR(battmgr->wls_psy), + "failed to register wireless charing power supply\n"); + } + + battmgr->client = devm_pmic_glink_register_client(dev, + PMIC_GLINK_OWNER_BATTMGR, + qcom_battmgr_callback, + qcom_battmgr_pdr_notify, + battmgr); + return PTR_ERR_OR_ZERO(battmgr->client); +} + +static const struct auxiliary_device_id qcom_battmgr_id_table[] = { + { .name = "pmic_glink.power-supply", }, + {}, +}; +MODULE_DEVICE_TABLE(auxiliary, qcom_battmgr_id_table); + +static struct auxiliary_driver qcom_battmgr_driver = { + .name = "pmic_glink_power_supply", + .probe = qcom_battmgr_probe, + .id_table = qcom_battmgr_id_table, +}; + +static int __init qcom_battmgr_init(void) +{ + return auxiliary_driver_register(&qcom_battmgr_driver); +} +module_init(qcom_battmgr_init); + +static void __exit qcom_battmgr_exit(void) +{ + auxiliary_driver_unregister(&qcom_battmgr_driver); +} +module_exit(qcom_battmgr_exit); + +MODULE_DESCRIPTION("Qualcomm PMIC GLINK battery manager driver"); +MODULE_LICENSE("GPL");