* [PATCH v2 0/4] soc: qcom: Introduce PMIC GLINK
@ 2023-01-13 4:11 Bjorn Andersson
2023-01-13 4:11 ` [PATCH v2 1/4] dt-bindings: soc: qcom: Introduce PMIC GLINK binding Bjorn Andersson
` (5 more replies)
0 siblings, 6 replies; 19+ messages in thread
From: Bjorn Andersson @ 2023-01-13 4:11 UTC (permalink / raw)
To: Andy Gross, Bjorn Andersson, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Sebastian Reichel
Cc: linux-arm-msm, devicetree, linux-kernel, linux-pm,
Subbaraman Narayanamurthy, Johan Hovold, Neil Armstrong
This implements the base PMIC GLINK driver, a power_supply driver and a
driver for the USB Type-C altmode protocol. This has been tested and
shown to provide battery information, USB Type-C switch and mux requests
and DisplayPort notifications on SC8180X, SC8280XP and SM8350.
Bjorn Andersson (4):
dt-bindings: soc: qcom: Introduce PMIC GLINK binding
soc: qcom: pmic_glink: Introduce base PMIC GLINK driver
soc: qcom: pmic_glink: Introduce altmode support
power: supply: Introduce Qualcomm PMIC GLINK power supply
.../bindings/soc/qcom/qcom,pmic-glink.yaml | 102 ++
drivers/power/supply/Kconfig | 9 +
drivers/power/supply/Makefile | 1 +
drivers/power/supply/qcom_battmgr.c | 1421 +++++++++++++++++
drivers/soc/qcom/Kconfig | 15 +
drivers/soc/qcom/Makefile | 2 +
drivers/soc/qcom/pmic_glink.c | 336 ++++
drivers/soc/qcom/pmic_glink_altmode.c | 477 ++++++
include/linux/soc/qcom/pmic_glink.h | 32 +
9 files changed, 2395 insertions(+)
create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml
create mode 100644 drivers/power/supply/qcom_battmgr.c
create mode 100644 drivers/soc/qcom/pmic_glink.c
create mode 100644 drivers/soc/qcom/pmic_glink_altmode.c
create mode 100644 include/linux/soc/qcom/pmic_glink.h
--
2.37.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH v2 1/4] dt-bindings: soc: qcom: Introduce PMIC GLINK binding 2023-01-13 4:11 [PATCH v2 0/4] soc: qcom: Introduce PMIC GLINK Bjorn Andersson @ 2023-01-13 4:11 ` Bjorn Andersson 2023-01-17 18:00 ` Rob Herring 2023-01-13 4:11 ` [PATCH v2 2/4] soc: qcom: pmic_glink: Introduce base PMIC GLINK driver Bjorn Andersson ` (4 subsequent siblings) 5 siblings, 1 reply; 19+ messages in thread From: Bjorn Andersson @ 2023-01-13 4:11 UTC (permalink / raw) To: Andy Gross, Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski, Sebastian Reichel Cc: linux-arm-msm, devicetree, linux-kernel, linux-pm, Subbaraman Narayanamurthy, Johan Hovold, Neil Armstrong From: Bjorn Andersson <bjorn.andersson@linaro.org> 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 <bjorn.andersson@linaro.org> Signed-off-by: Bjorn Andersson <quic_bjorande@quicinc.com> --- Changes since v1: - Added reg under connector, to identify multiple connectors - Updated maintainer email .../bindings/soc/qcom/qcom,pmic-glink.yaml | 102 ++++++++++++++++++ 1 file changed, 102 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..a79dd0ed9275 --- /dev/null +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml @@ -0,0 +1,102 @@ +# 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 + + 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>; + }; + }; + }; + }; + }; +... + -- 2.37.3 ^ permalink raw reply related [flat|nested] 19+ messages in thread
* Re: [PATCH v2 1/4] dt-bindings: soc: qcom: Introduce PMIC GLINK binding 2023-01-13 4:11 ` [PATCH v2 1/4] dt-bindings: soc: qcom: Introduce PMIC GLINK binding Bjorn Andersson @ 2023-01-17 18:00 ` Rob Herring 2023-01-18 18:12 ` Bjorn Andersson 0 siblings, 1 reply; 19+ messages in thread From: Rob Herring @ 2023-01-17 18:00 UTC (permalink / raw) To: Bjorn Andersson Cc: Andy Gross, Bjorn Andersson, Konrad Dybcio, Krzysztof Kozlowski, Sebastian Reichel, linux-arm-msm, devicetree, linux-kernel, linux-pm, Subbaraman Narayanamurthy, Johan Hovold, Neil Armstrong On Thu, Jan 12, 2023 at 08:11:29PM -0800, Bjorn Andersson wrote: > From: Bjorn Andersson <bjorn.andersson@linaro.org> > > 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 <bjorn.andersson@linaro.org> > Signed-off-by: Bjorn Andersson <quic_bjorande@quicinc.com> > --- > > Changes since v1: > - Added reg under connector, to identify multiple connectors > - Updated maintainer email > > .../bindings/soc/qcom/qcom,pmic-glink.yaml | 102 ++++++++++++++++++ > 1 file changed, 102 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..a79dd0ed9275 > --- /dev/null > +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml > @@ -0,0 +1,102 @@ > +# 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 Or '@\d' needs to be optional. > + > + 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>; > + }; > + }; > + }; > + }; > + }; > +... > + > -- > 2.37.3 > ^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v2 1/4] dt-bindings: soc: qcom: Introduce PMIC GLINK binding 2023-01-17 18:00 ` Rob Herring @ 2023-01-18 18:12 ` Bjorn Andersson 0 siblings, 0 replies; 19+ messages in thread From: Bjorn Andersson @ 2023-01-18 18:12 UTC (permalink / raw) To: Rob Herring Cc: Andy Gross, Bjorn Andersson, Konrad Dybcio, Krzysztof Kozlowski, Sebastian Reichel, linux-arm-msm, devicetree, linux-kernel, linux-pm, Subbaraman Narayanamurthy, Johan Hovold, Neil Armstrong On Tue, Jan 17, 2023 at 12:00:58PM -0600, Rob Herring wrote: > On Thu, Jan 12, 2023 at 08:11:29PM -0800, Bjorn Andersson wrote: [..] > > +patternProperties: > > + '^connector@\d$': > > + $ref: /schemas/connector/usb-connector.yaml# > > + > > + properties: > > + reg: true > > required: > - reg > > Or '@\d' needs to be optional. > You're right, and as the identifier is significant in the firmware interface I'll add it as required. Thanks, Bjorn ^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH v2 2/4] soc: qcom: pmic_glink: Introduce base PMIC GLINK driver 2023-01-13 4:11 [PATCH v2 0/4] soc: qcom: Introduce PMIC GLINK Bjorn Andersson 2023-01-13 4:11 ` [PATCH v2 1/4] dt-bindings: soc: qcom: Introduce PMIC GLINK binding Bjorn Andersson @ 2023-01-13 4:11 ` Bjorn Andersson 2023-01-13 4:11 ` [PATCH v2 3/4] soc: qcom: pmic_glink: Introduce altmode support Bjorn Andersson ` (3 subsequent siblings) 5 siblings, 0 replies; 19+ messages in thread From: Bjorn Andersson @ 2023-01-13 4:11 UTC (permalink / raw) To: Andy Gross, Bjorn Andersson, Konrad Dybcio Cc: Rob Herring, Krzysztof Kozlowski, Sebastian Reichel, linux-arm-msm, devicetree, linux-kernel, linux-pm, Subbaraman Narayanamurthy, Johan Hovold, Neil Armstrong From: Bjorn Andersson <bjorn.andersson@linaro.org> 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 <bjorn.andersson@linaro.org> Signed-off-by: Bjorn Andersson <quic_bjorande@quicinc.com> --- Changes since v1: - select AUXILIARY_BUS - Renamed pmic_glink_client->pmic -> pg, to make naming more uniform. - Swapped order in module_exit() to unroll in reverse order, per Krzysztof's suggestion.. 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 <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"); 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 -- 2.37.3 ^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v2 3/4] soc: qcom: pmic_glink: Introduce altmode support 2023-01-13 4:11 [PATCH v2 0/4] soc: qcom: Introduce PMIC GLINK Bjorn Andersson 2023-01-13 4:11 ` [PATCH v2 1/4] dt-bindings: soc: qcom: Introduce PMIC GLINK binding Bjorn Andersson 2023-01-13 4:11 ` [PATCH v2 2/4] soc: qcom: pmic_glink: Introduce base PMIC GLINK driver Bjorn Andersson @ 2023-01-13 4:11 ` Bjorn Andersson 2023-01-15 19:10 ` Steev Klimaszewski 2023-01-20 10:06 ` Neil Armstrong 2023-01-13 4:11 ` [PATCH v2 4/4] power: supply: Introduce Qualcomm PMIC GLINK power supply Bjorn Andersson ` (2 subsequent siblings) 5 siblings, 2 replies; 19+ messages in thread From: Bjorn Andersson @ 2023-01-13 4:11 UTC (permalink / raw) To: Andy Gross, Bjorn Andersson, Konrad Dybcio Cc: Rob Herring, Krzysztof Kozlowski, Sebastian Reichel, linux-arm-msm, devicetree, linux-kernel, linux-pm, Subbaraman Narayanamurthy, Johan Hovold, Neil Armstrong From: Bjorn Andersson <bjorn.andersson@linaro.org> 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 <bjorn.andersson@linaro.org> Signed-off-by: Bjorn Andersson <quic_bjorande@quicinc.com> --- Changes since v1: - None Johan reported a NULL pointer dereference in drm_kms_helper_hotplug_event() for HPD event being reported while the MSM DRM driver is still being initalized, a separate fix has been sent in hope to remidy this race condition in the MSM driver. drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/pmic_glink_altmode.c | 477 ++++++++++++++++++++++++++ 2 files changed, 478 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..8d2d563cb756 --- /dev/null +++ b/drivers/soc/qcom/pmic_glink_altmode.c @@ -0,0 +1,477 @@ +// 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/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"); -- 2.37.3 ^ permalink raw reply related [flat|nested] 19+ messages in thread
* Re: [PATCH v2 3/4] soc: qcom: pmic_glink: Introduce altmode support 2023-01-13 4:11 ` [PATCH v2 3/4] soc: qcom: pmic_glink: Introduce altmode support Bjorn Andersson @ 2023-01-15 19:10 ` Steev Klimaszewski 2023-01-17 2:08 ` Bjorn Andersson 2023-01-20 10:06 ` Neil Armstrong 1 sibling, 1 reply; 19+ messages in thread From: Steev Klimaszewski @ 2023-01-15 19:10 UTC (permalink / raw) To: Bjorn Andersson Cc: Andy Gross, Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski, Sebastian Reichel, linux-arm-msm, devicetree, linux-kernel, linux-pm, Subbaraman Narayanamurthy, Johan Hovold, Neil Armstrong On Thu, Jan 12, 2023 at 10:13 PM Bjorn Andersson <quic_bjorande@quicinc.com> wrote: > > From: Bjorn Andersson <bjorn.andersson@linaro.org> > > 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 <bjorn.andersson@linaro.org> > Signed-off-by: Bjorn Andersson <quic_bjorande@quicinc.com> > --- > > Changes since v1: > - None > > Johan reported a NULL pointer dereference in > drm_kms_helper_hotplug_event() for HPD event being reported while the > MSM DRM driver is still being initalized, a separate fix has been sent > in hope to remidy this race condition in the MSM driver. > > drivers/soc/qcom/Makefile | 1 + > drivers/soc/qcom/pmic_glink_altmode.c | 477 ++++++++++++++++++++++++++ > 2 files changed, 478 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..8d2d563cb756 > --- /dev/null > +++ b/drivers/soc/qcom/pmic_glink_altmode.c > @@ -0,0 +1,477 @@ > +// 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/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); > + The kernel test robot keeps complaining about these FIELD_GET because there is no #include <linux/bitfield.h> > + 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"); > -- > 2.37.3 > ^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v2 3/4] soc: qcom: pmic_glink: Introduce altmode support 2023-01-15 19:10 ` Steev Klimaszewski @ 2023-01-17 2:08 ` Bjorn Andersson 0 siblings, 0 replies; 19+ messages in thread From: Bjorn Andersson @ 2023-01-17 2:08 UTC (permalink / raw) To: Steev Klimaszewski Cc: Bjorn Andersson, Andy Gross, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski, Sebastian Reichel, linux-arm-msm, devicetree, linux-kernel, linux-pm, Subbaraman Narayanamurthy, Johan Hovold, Neil Armstrong On Sun, Jan 15, 2023 at 01:10:14PM -0600, Steev Klimaszewski wrote: > > diff --git a/drivers/soc/qcom/pmic_glink_altmode.c b/drivers/soc/qcom/pmic_glink_altmode.c [..] > > + 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); > > + > The kernel test robot keeps complaining about these FIELD_GET because > there is no #include <linux/bitfield.h> > I must have missed those complains before, thanks for pointing it out! Regards, Bjorn ^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v2 3/4] soc: qcom: pmic_glink: Introduce altmode support 2023-01-13 4:11 ` [PATCH v2 3/4] soc: qcom: pmic_glink: Introduce altmode support Bjorn Andersson 2023-01-15 19:10 ` Steev Klimaszewski @ 2023-01-20 10:06 ` Neil Armstrong 2023-01-20 10:33 ` Neil Armstrong 1 sibling, 1 reply; 19+ messages in thread From: Neil Armstrong @ 2023-01-20 10:06 UTC (permalink / raw) To: Bjorn Andersson, Andy Gross, Bjorn Andersson, Konrad Dybcio Cc: Rob Herring, Krzysztof Kozlowski, Sebastian Reichel, linux-arm-msm, devicetree, linux-kernel, linux-pm, Subbaraman Narayanamurthy, Johan Hovold On 13/01/2023 05:11, Bjorn Andersson wrote: > From: Bjorn Andersson <bjorn.andersson@linaro.org> > > 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 <bjorn.andersson@linaro.org> > Signed-off-by: Bjorn Andersson <quic_bjorande@quicinc.com> > --- > > Changes since v1: > - None > > Johan reported a NULL pointer dereference in > drm_kms_helper_hotplug_event() for HPD event being reported while the > MSM DRM driver is still being initalized, a separate fix has been sent > in hope to remidy this race condition in the MSM driver. > > drivers/soc/qcom/Makefile | 1 + > drivers/soc/qcom/pmic_glink_altmode.c | 477 ++++++++++++++++++++++++++ > 2 files changed, 478 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..8d2d563cb756 > --- /dev/null > +++ b/drivers/soc/qcom/pmic_glink_altmode.c > @@ -0,0 +1,477 @@ > +// 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/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; In my testing, the design of a bridge in the altmode driver made all the probe very fragile, meaning that any device that won't probe in the full usb--pmic-glink--display driver would prevent the whole to actually probe. This is why drm_connector_oob_hotplug_event() was used in drivers/usb/typec/altmodes/displayport.c and a similar design for cec where both attach to a device so there's no probe dependency. I think there's a possible simplification here by using : of_drm_find_bridge() instead on the endpoint target to get the last bridge, or we could probably use the same drm_connector_oob_hotplug_event() but in any way we should add missing pieces in drm_bridge_connector and drm/msm/dp/dp_drm.c First in drm/msm/dp/dp_drm.c, the driver should add the of_node to the DP bridge so it can be found from the dp controller node. Secondly, we could add a fwnode in drm_bridge like in drm_connector and in drm_bridge_connector_init() we could set the connector fwnode to the last bridge fwnode + the connector oob event handler. In this case the drm_connector_oob_hotplug_event() design should work. Anyway, I think the bindings is correct, so it's a matter of implementation to avoid delaying the display driver probe until the pmic_glink probes entirely. Neil > + > + 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"); ^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v2 3/4] soc: qcom: pmic_glink: Introduce altmode support 2023-01-20 10:06 ` Neil Armstrong @ 2023-01-20 10:33 ` Neil Armstrong 0 siblings, 0 replies; 19+ messages in thread From: Neil Armstrong @ 2023-01-20 10:33 UTC (permalink / raw) To: Bjorn Andersson, Andy Gross, Bjorn Andersson, Konrad Dybcio Cc: Rob Herring, Krzysztof Kozlowski, Sebastian Reichel, linux-arm-msm, devicetree, linux-kernel, linux-pm, Subbaraman Narayanamurthy, Johan Hovold On 20/01/2023 11:06, Neil Armstrong wrote: > On 13/01/2023 05:11, Bjorn Andersson wrote: >> From: Bjorn Andersson <bjorn.andersson@linaro.org> >> >> 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 <bjorn.andersson@linaro.org> >> Signed-off-by: Bjorn Andersson <quic_bjorande@quicinc.com> >> --- >> >> Changes since v1: >> - None >> >> Johan reported a NULL pointer dereference in >> drm_kms_helper_hotplug_event() for HPD event being reported while the >> MSM DRM driver is still being initalized, a separate fix has been sent >> in hope to remidy this race condition in the MSM driver. >> >> drivers/soc/qcom/Makefile | 1 + >> drivers/soc/qcom/pmic_glink_altmode.c | 477 ++++++++++++++++++++++++++ >> 2 files changed, 478 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..8d2d563cb756 >> --- /dev/null >> +++ b/drivers/soc/qcom/pmic_glink_altmode.c >> @@ -0,0 +1,477 @@ >> +// 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/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; > > In my testing, the design of a bridge in the altmode driver made all the probe very fragile, > meaning that any device that won't probe in the full usb--pmic-glink--display driver would > prevent the whole to actually probe. > > This is why drm_connector_oob_hotplug_event() was used in drivers/usb/typec/altmodes/displayport.c > and a similar design for cec where both attach to a device so there's no probe dependency. > > I think there's a possible simplification here by using : > of_drm_find_bridge() instead on the endpoint target to get the last bridge, > or we could probably use the same drm_connector_oob_hotplug_event() but in any way we should > add missing pieces in drm_bridge_connector and drm/msm/dp/dp_drm.c > > First in drm/msm/dp/dp_drm.c, the driver should add the of_node to the DP bridge so it can > be found from the dp controller node. > > Secondly, we could add a fwnode in drm_bridge like in drm_connector and in drm_bridge_connector_init() > we could set the connector fwnode to the last bridge fwnode + the connector oob event handler. > In this case the drm_connector_oob_hotplug_event() design should work. > > Anyway, I think the bindings is correct, so it's a matter of implementation to avoid delaying the > display driver probe until the pmic_glink probes entirely. While looking closely, I don't think we can design differently except moving the bridge code into a dummy bridge into gpu/drm/bridge, but this won't solve anything but make things even more complex. This design follows how generic display connector bridge is designed like in gpu/drm/bridge/display-connector.c so it's valid. So for the DRM Bridge part: Acked-by: Neil Armstrong <neil.armstrong@linaro.org> Neil > > Neil > >> + >> + 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"); > ^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH v2 4/4] power: supply: Introduce Qualcomm PMIC GLINK power supply 2023-01-13 4:11 [PATCH v2 0/4] soc: qcom: Introduce PMIC GLINK Bjorn Andersson ` (2 preceding siblings ...) 2023-01-13 4:11 ` [PATCH v2 3/4] soc: qcom: pmic_glink: Introduce altmode support Bjorn Andersson @ 2023-01-13 4:11 ` Bjorn Andersson 2023-01-13 14:56 ` [PATCH v2 0/4] soc: qcom: Introduce PMIC GLINK Konrad Dybcio 2023-01-13 17:10 ` Bryan O'Donoghue 5 siblings, 0 replies; 19+ messages in thread From: Bjorn Andersson @ 2023-01-13 4:11 UTC (permalink / raw) To: Andy Gross, Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski, Sebastian Reichel Cc: linux-arm-msm, devicetree, linux-kernel, linux-pm, Subbaraman Narayanamurthy, Johan Hovold, Neil Armstrong From: Bjorn Andersson <bjorn.andersson@linaro.org> 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 <bjorn.andersson@linaro.org> Signed-off-by: Bjorn Andersson <quic_bjorande@quicinc.com> --- Changes since v1: - Reordered intialization, to register power-supplies before bringin up the pmic_glink, to avoid issues with receving notifications before the power supplies are available. Iusses with opposite were already handled... - Handle battery information update notifications (0x81), instead of complaining in the log. - Report POWER_SUPPLY_PROP_PRESENT for sc8280xp, if any battery information has been received. 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 <linux/auxiliary_bus.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of_device.h> +#include <linux/power_supply.h> +#include <linux/soc/qcom/pdr.h> +#include <linux/soc/qcom/pmic_glink.h> +#include <linux/math.h> +#include <linux/units.h> + +#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"); -- 2.37.3 ^ permalink raw reply related [flat|nested] 19+ messages in thread
* Re: [PATCH v2 0/4] soc: qcom: Introduce PMIC GLINK 2023-01-13 4:11 [PATCH v2 0/4] soc: qcom: Introduce PMIC GLINK Bjorn Andersson ` (3 preceding siblings ...) 2023-01-13 4:11 ` [PATCH v2 4/4] power: supply: Introduce Qualcomm PMIC GLINK power supply Bjorn Andersson @ 2023-01-13 14:56 ` Konrad Dybcio 2023-01-17 2:13 ` Bjorn Andersson 2023-01-13 17:10 ` Bryan O'Donoghue 5 siblings, 1 reply; 19+ messages in thread From: Konrad Dybcio @ 2023-01-13 14:56 UTC (permalink / raw) To: Bjorn Andersson, Andy Gross, Bjorn Andersson, Rob Herring, Krzysztof Kozlowski, Sebastian Reichel Cc: linux-arm-msm, devicetree, linux-kernel, linux-pm, Subbaraman Narayanamurthy, Johan Hovold, Neil Armstrong On 13.01.2023 05:11, Bjorn Andersson wrote: > This implements the base PMIC GLINK driver, a power_supply driver and a > driver for the USB Type-C altmode protocol. This has been tested and > shown to provide battery information, USB Type-C switch and mux requests > and DisplayPort notifications on SC8180X, SC8280XP and SM8350. > For the series: Tested-by: Konrad Dybcio <konrad.dybcio@linaro.org> # SM8350 PDX215 Thanks a lot for working on this! One thing, /sys/class/power_supply/qcom-battmgr-usb/input_current_limit is stuck at zero and so is the current_now as a result (the voltage readout is 5V + some noise, so that looks good), but I don't see any SET paths for it in the driver, so I suppose that's what the firmware default is? Konrad > Bjorn Andersson (4): > dt-bindings: soc: qcom: Introduce PMIC GLINK binding > soc: qcom: pmic_glink: Introduce base PMIC GLINK driver > soc: qcom: pmic_glink: Introduce altmode support > power: supply: Introduce Qualcomm PMIC GLINK power supply > > .../bindings/soc/qcom/qcom,pmic-glink.yaml | 102 ++ > drivers/power/supply/Kconfig | 9 + > drivers/power/supply/Makefile | 1 + > drivers/power/supply/qcom_battmgr.c | 1421 +++++++++++++++++ > drivers/soc/qcom/Kconfig | 15 + > drivers/soc/qcom/Makefile | 2 + > drivers/soc/qcom/pmic_glink.c | 336 ++++ > drivers/soc/qcom/pmic_glink_altmode.c | 477 ++++++ > include/linux/soc/qcom/pmic_glink.h | 32 + > 9 files changed, 2395 insertions(+) > create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml > create mode 100644 drivers/power/supply/qcom_battmgr.c > create mode 100644 drivers/soc/qcom/pmic_glink.c > create mode 100644 drivers/soc/qcom/pmic_glink_altmode.c > create mode 100644 include/linux/soc/qcom/pmic_glink.h > ^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v2 0/4] soc: qcom: Introduce PMIC GLINK 2023-01-13 14:56 ` [PATCH v2 0/4] soc: qcom: Introduce PMIC GLINK Konrad Dybcio @ 2023-01-17 2:13 ` Bjorn Andersson 0 siblings, 0 replies; 19+ messages in thread From: Bjorn Andersson @ 2023-01-17 2:13 UTC (permalink / raw) To: Konrad Dybcio Cc: Andy Gross, Bjorn Andersson, Rob Herring, Krzysztof Kozlowski, Sebastian Reichel, linux-arm-msm, devicetree, linux-kernel, linux-pm, Subbaraman Narayanamurthy, Johan Hovold, Neil Armstrong On Fri, Jan 13, 2023 at 03:56:59PM +0100, Konrad Dybcio wrote: > > > On 13.01.2023 05:11, Bjorn Andersson wrote: > > This implements the base PMIC GLINK driver, a power_supply driver and a > > driver for the USB Type-C altmode protocol. This has been tested and > > shown to provide battery information, USB Type-C switch and mux requests > > and DisplayPort notifications on SC8180X, SC8280XP and SM8350. > > > For the series: > > Tested-by: Konrad Dybcio <konrad.dybcio@linaro.org> # SM8350 PDX215 > > Thanks a lot for working on this! > Thank you for testing it :) > One thing, /sys/class/power_supply/qcom-battmgr-usb/input_current_limit > is stuck at zero and so is the current_now as a result (the voltage > readout is 5V + some noise, so that looks good), but I don't see any > SET paths for it in the driver, so I suppose that's what the firmware > default is? > I have not experimented with adjusting any configuration in this initial set, but there are a few knobs that could/should be introduced on top of this. That said, I believe input_current_limit should somehow come from the USB stack, rather than user space? Regards, Bjorn ^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v2 0/4] soc: qcom: Introduce PMIC GLINK 2023-01-13 4:11 [PATCH v2 0/4] soc: qcom: Introduce PMIC GLINK Bjorn Andersson ` (4 preceding siblings ...) 2023-01-13 14:56 ` [PATCH v2 0/4] soc: qcom: Introduce PMIC GLINK Konrad Dybcio @ 2023-01-13 17:10 ` Bryan O'Donoghue 2023-01-17 2:32 ` Bjorn Andersson 5 siblings, 1 reply; 19+ messages in thread From: Bryan O'Donoghue @ 2023-01-13 17:10 UTC (permalink / raw) To: Bjorn Andersson, Andy Gross, Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski, Sebastian Reichel Cc: linux-arm-msm, devicetree, linux-kernel, linux-pm, Subbaraman Narayanamurthy, Johan Hovold, Neil Armstrong On 13/01/2023 04:11, Bjorn Andersson wrote: > This implements the base PMIC GLINK driver, a power_supply driver and a > driver for the USB Type-C altmode protocol. This has been tested and > shown to provide battery information, USB Type-C switch and mux requests > and DisplayPort notifications on SC8180X, SC8280XP and SM8350. > > Bjorn Andersson (4): > dt-bindings: soc: qcom: Introduce PMIC GLINK binding > soc: qcom: pmic_glink: Introduce base PMIC GLINK driver > soc: qcom: pmic_glink: Introduce altmode support > power: supply: Introduce Qualcomm PMIC GLINK power supply > > .../bindings/soc/qcom/qcom,pmic-glink.yaml | 102 ++ > drivers/power/supply/Kconfig | 9 + > drivers/power/supply/Makefile | 1 + > drivers/power/supply/qcom_battmgr.c | 1421 +++++++++++++++++ > drivers/soc/qcom/Kconfig | 15 + > drivers/soc/qcom/Makefile | 2 + > drivers/soc/qcom/pmic_glink.c | 336 ++++ > drivers/soc/qcom/pmic_glink_altmode.c | 477 ++++++ > include/linux/soc/qcom/pmic_glink.h | 32 + > 9 files changed, 2395 insertions(+) > create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml > create mode 100644 drivers/power/supply/qcom_battmgr.c > create mode 100644 drivers/soc/qcom/pmic_glink.c > create mode 100644 drivers/soc/qcom/pmic_glink_altmode.c > create mode 100644 include/linux/soc/qcom/pmic_glink.h > How does the USB PHY and a USB redriver fit into this ? Is the host supposed to manage both/neither ? Is the DSP responsible for configuring the PHY lanes and the turnaround on orientation switch ? --- bod ^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v2 0/4] soc: qcom: Introduce PMIC GLINK 2023-01-13 17:10 ` Bryan O'Donoghue @ 2023-01-17 2:32 ` Bjorn Andersson 2023-01-17 2:37 ` Bryan O'Donoghue 0 siblings, 1 reply; 19+ messages in thread From: Bjorn Andersson @ 2023-01-17 2:32 UTC (permalink / raw) To: Bryan O'Donoghue Cc: Andy Gross, Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski, Sebastian Reichel, linux-arm-msm, devicetree, linux-kernel, linux-pm, Subbaraman Narayanamurthy, Johan Hovold, Neil Armstrong On Fri, Jan 13, 2023 at 05:10:17PM +0000, Bryan O'Donoghue wrote: > On 13/01/2023 04:11, Bjorn Andersson wrote: > > This implements the base PMIC GLINK driver, a power_supply driver and a > > driver for the USB Type-C altmode protocol. This has been tested and > > shown to provide battery information, USB Type-C switch and mux requests > > and DisplayPort notifications on SC8180X, SC8280XP and SM8350. > > > > Bjorn Andersson (4): > > dt-bindings: soc: qcom: Introduce PMIC GLINK binding > > soc: qcom: pmic_glink: Introduce base PMIC GLINK driver > > soc: qcom: pmic_glink: Introduce altmode support > > power: supply: Introduce Qualcomm PMIC GLINK power supply > > > > .../bindings/soc/qcom/qcom,pmic-glink.yaml | 102 ++ > > drivers/power/supply/Kconfig | 9 + > > drivers/power/supply/Makefile | 1 + > > drivers/power/supply/qcom_battmgr.c | 1421 +++++++++++++++++ > > drivers/soc/qcom/Kconfig | 15 + > > drivers/soc/qcom/Makefile | 2 + > > drivers/soc/qcom/pmic_glink.c | 336 ++++ > > drivers/soc/qcom/pmic_glink_altmode.c | 477 ++++++ > > include/linux/soc/qcom/pmic_glink.h | 32 + > > 9 files changed, 2395 insertions(+) > > create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml > > create mode 100644 drivers/power/supply/qcom_battmgr.c > > create mode 100644 drivers/soc/qcom/pmic_glink.c > > create mode 100644 drivers/soc/qcom/pmic_glink_altmode.c > > create mode 100644 include/linux/soc/qcom/pmic_glink.h > > > > How does the USB PHY and a USB redriver fit into this ? > > Is the host supposed to manage both/neither ? Is the DSP responsible for > configuring the PHY lanes and the turnaround on orientation switch ? > As indicated above, the firmware deals with battery management and USB Type-C handling. The battery/power management is handled by the battmgr implementation, exposing the various properties through a set of power_supply objects. The USB Type-C handling comes in two forms. The "altmode" protocol handles DisplayPort notifications - plug detect, orientation and mode switches. The other part of the USB implementation exposes UCSI. The altmode implementation provides two things: - A drm_bridge, per connector, which can be tied (of_graph) to a DisplayPort instance, and will invoke HPD notifications on the drm_bridge, based on notification messages thereof. - Acquire typec_switch and typec_mux handles through the of_graph and signal the remotes when notifications of state changes occur. Linking this to the FSA4480, is sufficient to get USB/DP combo (2+2 lanes) working on e.g. SM8350 HDK. Work in progress patches also exists for teaching QMP about orientation switching of the SS lines, but it seems this needs to be rebased onto the refactored QMP driver. I also have patches for QMP to make it switch USB/DP combo -> 4-lane DP, which allow 4k support without DSC, unfortunately switch back to USB has not been fully reliable, so this requires some more work (downstream involves DWC3 here as well, to reprogram the PHY). I have been experimenting with UCSI in the past, but my goal for this series was to support external displays on my desktop (laptop...), but through some experiments I've wired the connectors to dwc3 in order to get usb_role_switch working. Neil has been looking at this in more detail lately though. Regards, Bjorn ^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v2 0/4] soc: qcom: Introduce PMIC GLINK 2023-01-17 2:32 ` Bjorn Andersson @ 2023-01-17 2:37 ` Bryan O'Donoghue 2023-01-17 2:58 ` Bjorn Andersson 0 siblings, 1 reply; 19+ messages in thread From: Bryan O'Donoghue @ 2023-01-17 2:37 UTC (permalink / raw) To: Bjorn Andersson Cc: Andy Gross, Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski, Sebastian Reichel, linux-arm-msm, devicetree, linux-kernel, linux-pm, Subbaraman Narayanamurthy, Johan Hovold, Neil Armstrong On 17/01/2023 02:32, Bjorn Andersson wrote: > On Fri, Jan 13, 2023 at 05:10:17PM +0000, Bryan O'Donoghue wrote: >> On 13/01/2023 04:11, Bjorn Andersson wrote: >>> This implements the base PMIC GLINK driver, a power_supply driver and a >>> driver for the USB Type-C altmode protocol. This has been tested and >>> shown to provide battery information, USB Type-C switch and mux requests >>> and DisplayPort notifications on SC8180X, SC8280XP and SM8350. >>> >>> Bjorn Andersson (4): >>> dt-bindings: soc: qcom: Introduce PMIC GLINK binding >>> soc: qcom: pmic_glink: Introduce base PMIC GLINK driver >>> soc: qcom: pmic_glink: Introduce altmode support >>> power: supply: Introduce Qualcomm PMIC GLINK power supply >>> >>> .../bindings/soc/qcom/qcom,pmic-glink.yaml | 102 ++ >>> drivers/power/supply/Kconfig | 9 + >>> drivers/power/supply/Makefile | 1 + >>> drivers/power/supply/qcom_battmgr.c | 1421 +++++++++++++++++ >>> drivers/soc/qcom/Kconfig | 15 + >>> drivers/soc/qcom/Makefile | 2 + >>> drivers/soc/qcom/pmic_glink.c | 336 ++++ >>> drivers/soc/qcom/pmic_glink_altmode.c | 477 ++++++ >>> include/linux/soc/qcom/pmic_glink.h | 32 + >>> 9 files changed, 2395 insertions(+) >>> create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml >>> create mode 100644 drivers/power/supply/qcom_battmgr.c >>> create mode 100644 drivers/soc/qcom/pmic_glink.c >>> create mode 100644 drivers/soc/qcom/pmic_glink_altmode.c >>> create mode 100644 include/linux/soc/qcom/pmic_glink.h >>> >> >> How does the USB PHY and a USB redriver fit into this ? >> >> Is the host supposed to manage both/neither ? Is the DSP responsible for >> configuring the PHY lanes and the turnaround on orientation switch ? >> > > As indicated above, the firmware deals with battery management and USB > Type-C handling. > > The battery/power management is handled by the battmgr implementation, > exposing the various properties through a set of power_supply objects. > > The USB Type-C handling comes in two forms. The "altmode" protocol > handles DisplayPort notifications - plug detect, orientation and mode > switches. The other part of the USB implementation exposes UCSI. > > The altmode implementation provides two things: > - A drm_bridge, per connector, which can be tied (of_graph) to a > DisplayPort instance, and will invoke HPD notifications on the > drm_bridge, based on notification messages thereof. > > - Acquire typec_switch and typec_mux handles through the of_graph and > signal the remotes when notifications of state changes occur. Linking > this to the FSA4480, is sufficient to get USB/DP combo (2+2 lanes) > working on e.g. SM8350 HDK. > Work in progress patches also exists for teaching QMP about > orientation switching of the SS lines, but it seems this needs to be > rebased onto the refactored QMP driver. > I also have patches for QMP to make it switch USB/DP combo -> 4-lane > DP, which allow 4k support without DSC, unfortunately switch back to > USB has not been fully reliable, so this requires some more work > (downstream involves DWC3 here as well, to reprogram the PHY). Oki doki that makes sense and is pretty much in-line with what I thought. We still have a bunch of typec-mux and phy work to do even with adsp/glink doing the TCPM. Thanks for the explanation. --- bod ^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v2 0/4] soc: qcom: Introduce PMIC GLINK 2023-01-17 2:37 ` Bryan O'Donoghue @ 2023-01-17 2:58 ` Bjorn Andersson 2023-01-17 9:26 ` Dmitry Baryshkov 0 siblings, 1 reply; 19+ messages in thread From: Bjorn Andersson @ 2023-01-17 2:58 UTC (permalink / raw) To: Bryan O'Donoghue Cc: Andy Gross, Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski, Sebastian Reichel, linux-arm-msm, devicetree, linux-kernel, linux-pm, Subbaraman Narayanamurthy, Johan Hovold, Neil Armstrong On Tue, Jan 17, 2023 at 02:37:27AM +0000, Bryan O'Donoghue wrote: > On 17/01/2023 02:32, Bjorn Andersson wrote: > > On Fri, Jan 13, 2023 at 05:10:17PM +0000, Bryan O'Donoghue wrote: > > > On 13/01/2023 04:11, Bjorn Andersson wrote: > > > > This implements the base PMIC GLINK driver, a power_supply driver and a > > > > driver for the USB Type-C altmode protocol. This has been tested and > > > > shown to provide battery information, USB Type-C switch and mux requests > > > > and DisplayPort notifications on SC8180X, SC8280XP and SM8350. > > > > > > > > Bjorn Andersson (4): > > > > dt-bindings: soc: qcom: Introduce PMIC GLINK binding > > > > soc: qcom: pmic_glink: Introduce base PMIC GLINK driver > > > > soc: qcom: pmic_glink: Introduce altmode support > > > > power: supply: Introduce Qualcomm PMIC GLINK power supply > > > > > > > > .../bindings/soc/qcom/qcom,pmic-glink.yaml | 102 ++ > > > > drivers/power/supply/Kconfig | 9 + > > > > drivers/power/supply/Makefile | 1 + > > > > drivers/power/supply/qcom_battmgr.c | 1421 +++++++++++++++++ > > > > drivers/soc/qcom/Kconfig | 15 + > > > > drivers/soc/qcom/Makefile | 2 + > > > > drivers/soc/qcom/pmic_glink.c | 336 ++++ > > > > drivers/soc/qcom/pmic_glink_altmode.c | 477 ++++++ > > > > include/linux/soc/qcom/pmic_glink.h | 32 + > > > > 9 files changed, 2395 insertions(+) > > > > create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml > > > > create mode 100644 drivers/power/supply/qcom_battmgr.c > > > > create mode 100644 drivers/soc/qcom/pmic_glink.c > > > > create mode 100644 drivers/soc/qcom/pmic_glink_altmode.c > > > > create mode 100644 include/linux/soc/qcom/pmic_glink.h > > > > > > > > > > How does the USB PHY and a USB redriver fit into this ? > > > > > > Is the host supposed to manage both/neither ? Is the DSP responsible for > > > configuring the PHY lanes and the turnaround on orientation switch ? > > > > > > > As indicated above, the firmware deals with battery management and USB > > Type-C handling. > > > > The battery/power management is handled by the battmgr implementation, > > exposing the various properties through a set of power_supply objects. > > > > The USB Type-C handling comes in two forms. The "altmode" protocol > > handles DisplayPort notifications - plug detect, orientation and mode > > switches. The other part of the USB implementation exposes UCSI. > > > > The altmode implementation provides two things: > > - A drm_bridge, per connector, which can be tied (of_graph) to a > > DisplayPort instance, and will invoke HPD notifications on the > > drm_bridge, based on notification messages thereof. > > > > - Acquire typec_switch and typec_mux handles through the of_graph and > > signal the remotes when notifications of state changes occur. Linking > > this to the FSA4480, is sufficient to get USB/DP combo (2+2 lanes) > > working on e.g. SM8350 HDK. > > Work in progress patches also exists for teaching QMP about > > orientation switching of the SS lines, but it seems this needs to be > > rebased onto the refactored QMP driver. > > I also have patches for QMP to make it switch USB/DP combo -> 4-lane > > DP, which allow 4k support without DSC, unfortunately switch back to > > USB has not been fully reliable, so this requires some more work > > (downstream involves DWC3 here as well, to reprogram the PHY). > > Oki doki that makes sense and is pretty much in-line with what I thought. > > We still have a bunch of typec-mux and phy work to do even with adsp/glink > doing the TCPM. > Correct, the registration of QMP as a typec_switch and typec_mux and handling of respective notification remains open and should (by design) be independent of the TCPM implementation. In particular the orientation switching is an itch worth scratching at this time. But when the DPU becomes capable of producing 4k@60 output it would obviously be nice to have the whole shebang :) Regards, Bjorn > Thanks for the explanation. > > --- > bod > ^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v2 0/4] soc: qcom: Introduce PMIC GLINK 2023-01-17 2:58 ` Bjorn Andersson @ 2023-01-17 9:26 ` Dmitry Baryshkov 2023-01-17 15:48 ` Bjorn Andersson 0 siblings, 1 reply; 19+ messages in thread From: Dmitry Baryshkov @ 2023-01-17 9:26 UTC (permalink / raw) To: Bjorn Andersson, Bryan O'Donoghue Cc: Andy Gross, Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski, Sebastian Reichel, linux-arm-msm, devicetree, linux-kernel, linux-pm, Subbaraman Narayanamurthy, Johan Hovold, Neil Armstrong On 17/01/2023 04:58, Bjorn Andersson wrote: > On Tue, Jan 17, 2023 at 02:37:27AM +0000, Bryan O'Donoghue wrote: >> On 17/01/2023 02:32, Bjorn Andersson wrote: >>> On Fri, Jan 13, 2023 at 05:10:17PM +0000, Bryan O'Donoghue wrote: >>>> On 13/01/2023 04:11, Bjorn Andersson wrote: >>>>> This implements the base PMIC GLINK driver, a power_supply driver and a >>>>> driver for the USB Type-C altmode protocol. This has been tested and >>>>> shown to provide battery information, USB Type-C switch and mux requests >>>>> and DisplayPort notifications on SC8180X, SC8280XP and SM8350. >>>>> >>>>> Bjorn Andersson (4): >>>>> dt-bindings: soc: qcom: Introduce PMIC GLINK binding >>>>> soc: qcom: pmic_glink: Introduce base PMIC GLINK driver >>>>> soc: qcom: pmic_glink: Introduce altmode support >>>>> power: supply: Introduce Qualcomm PMIC GLINK power supply >>>>> >>>>> .../bindings/soc/qcom/qcom,pmic-glink.yaml | 102 ++ >>>>> drivers/power/supply/Kconfig | 9 + >>>>> drivers/power/supply/Makefile | 1 + >>>>> drivers/power/supply/qcom_battmgr.c | 1421 +++++++++++++++++ >>>>> drivers/soc/qcom/Kconfig | 15 + >>>>> drivers/soc/qcom/Makefile | 2 + >>>>> drivers/soc/qcom/pmic_glink.c | 336 ++++ >>>>> drivers/soc/qcom/pmic_glink_altmode.c | 477 ++++++ >>>>> include/linux/soc/qcom/pmic_glink.h | 32 + >>>>> 9 files changed, 2395 insertions(+) >>>>> create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml >>>>> create mode 100644 drivers/power/supply/qcom_battmgr.c >>>>> create mode 100644 drivers/soc/qcom/pmic_glink.c >>>>> create mode 100644 drivers/soc/qcom/pmic_glink_altmode.c >>>>> create mode 100644 include/linux/soc/qcom/pmic_glink.h >>>>> >>>> >>>> How does the USB PHY and a USB redriver fit into this ? >>>> >>>> Is the host supposed to manage both/neither ? Is the DSP responsible for >>>> configuring the PHY lanes and the turnaround on orientation switch ? >>>> >>> >>> As indicated above, the firmware deals with battery management and USB >>> Type-C handling. >>> >>> The battery/power management is handled by the battmgr implementation, >>> exposing the various properties through a set of power_supply objects. >>> >>> The USB Type-C handling comes in two forms. The "altmode" protocol >>> handles DisplayPort notifications - plug detect, orientation and mode >>> switches. The other part of the USB implementation exposes UCSI. >>> >>> The altmode implementation provides two things: >>> - A drm_bridge, per connector, which can be tied (of_graph) to a >>> DisplayPort instance, and will invoke HPD notifications on the >>> drm_bridge, based on notification messages thereof. >>> >>> - Acquire typec_switch and typec_mux handles through the of_graph and >>> signal the remotes when notifications of state changes occur. Linking >>> this to the FSA4480, is sufficient to get USB/DP combo (2+2 lanes) >>> working on e.g. SM8350 HDK. >>> Work in progress patches also exists for teaching QMP about >>> orientation switching of the SS lines, but it seems this needs to be >>> rebased onto the refactored QMP driver. >>> I also have patches for QMP to make it switch USB/DP combo -> 4-lane >>> DP, which allow 4k support without DSC, unfortunately switch back to >>> USB has not been fully reliable, so this requires some more work >>> (downstream involves DWC3 here as well, to reprogram the PHY). >> >> Oki doki that makes sense and is pretty much in-line with what I thought. >> >> We still have a bunch of typec-mux and phy work to do even with adsp/glink >> doing the TCPM. >> > > Correct, the registration of QMP as a typec_switch and typec_mux and > handling of respective notification remains open and should (by design) > be independent of the TCPM implementation. > > In particular the orientation switching is an itch worth scratching at > this time. But when the DPU becomes capable of producing 4k@60 output it > would obviously be nice to have the whole shebang :) Did you try it with the wide planes patchset at [1]? I was able to get stable 4k@30 on RB3 (being limited only by the DSI-HDMI bridge). [1] https://lore.kernel.org/linux-arm-msm/20221229191856.3508092-1-dmitry.baryshkov@linaro.org/ -- With best wishes Dmitry ^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v2 0/4] soc: qcom: Introduce PMIC GLINK 2023-01-17 9:26 ` Dmitry Baryshkov @ 2023-01-17 15:48 ` Bjorn Andersson 0 siblings, 0 replies; 19+ messages in thread From: Bjorn Andersson @ 2023-01-17 15:48 UTC (permalink / raw) To: Dmitry Baryshkov Cc: Bryan O'Donoghue, Andy Gross, Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski, Sebastian Reichel, linux-arm-msm, devicetree, linux-kernel, linux-pm, Subbaraman Narayanamurthy, Johan Hovold, Neil Armstrong On Tue, Jan 17, 2023 at 11:26:58AM +0200, Dmitry Baryshkov wrote: > On 17/01/2023 04:58, Bjorn Andersson wrote: > > On Tue, Jan 17, 2023 at 02:37:27AM +0000, Bryan O'Donoghue wrote: > > > On 17/01/2023 02:32, Bjorn Andersson wrote: > > > > On Fri, Jan 13, 2023 at 05:10:17PM +0000, Bryan O'Donoghue wrote: > > > > > On 13/01/2023 04:11, Bjorn Andersson wrote: > > > > > > This implements the base PMIC GLINK driver, a power_supply driver and a > > > > > > driver for the USB Type-C altmode protocol. This has been tested and > > > > > > shown to provide battery information, USB Type-C switch and mux requests > > > > > > and DisplayPort notifications on SC8180X, SC8280XP and SM8350. > > > > > > > > > > > > Bjorn Andersson (4): > > > > > > dt-bindings: soc: qcom: Introduce PMIC GLINK binding > > > > > > soc: qcom: pmic_glink: Introduce base PMIC GLINK driver > > > > > > soc: qcom: pmic_glink: Introduce altmode support > > > > > > power: supply: Introduce Qualcomm PMIC GLINK power supply > > > > > > > > > > > > .../bindings/soc/qcom/qcom,pmic-glink.yaml | 102 ++ > > > > > > drivers/power/supply/Kconfig | 9 + > > > > > > drivers/power/supply/Makefile | 1 + > > > > > > drivers/power/supply/qcom_battmgr.c | 1421 +++++++++++++++++ > > > > > > drivers/soc/qcom/Kconfig | 15 + > > > > > > drivers/soc/qcom/Makefile | 2 + > > > > > > drivers/soc/qcom/pmic_glink.c | 336 ++++ > > > > > > drivers/soc/qcom/pmic_glink_altmode.c | 477 ++++++ > > > > > > include/linux/soc/qcom/pmic_glink.h | 32 + > > > > > > 9 files changed, 2395 insertions(+) > > > > > > create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml > > > > > > create mode 100644 drivers/power/supply/qcom_battmgr.c > > > > > > create mode 100644 drivers/soc/qcom/pmic_glink.c > > > > > > create mode 100644 drivers/soc/qcom/pmic_glink_altmode.c > > > > > > create mode 100644 include/linux/soc/qcom/pmic_glink.h > > > > > > > > > > > > > > > > How does the USB PHY and a USB redriver fit into this ? > > > > > > > > > > Is the host supposed to manage both/neither ? Is the DSP responsible for > > > > > configuring the PHY lanes and the turnaround on orientation switch ? > > > > > > > > > > > > > As indicated above, the firmware deals with battery management and USB > > > > Type-C handling. > > > > > > > > The battery/power management is handled by the battmgr implementation, > > > > exposing the various properties through a set of power_supply objects. > > > > > > > > The USB Type-C handling comes in two forms. The "altmode" protocol > > > > handles DisplayPort notifications - plug detect, orientation and mode > > > > switches. The other part of the USB implementation exposes UCSI. > > > > > > > > The altmode implementation provides two things: > > > > - A drm_bridge, per connector, which can be tied (of_graph) to a > > > > DisplayPort instance, and will invoke HPD notifications on the > > > > drm_bridge, based on notification messages thereof. > > > > > > > > - Acquire typec_switch and typec_mux handles through the of_graph and > > > > signal the remotes when notifications of state changes occur. Linking > > > > this to the FSA4480, is sufficient to get USB/DP combo (2+2 lanes) > > > > working on e.g. SM8350 HDK. > > > > Work in progress patches also exists for teaching QMP about > > > > orientation switching of the SS lines, but it seems this needs to be > > > > rebased onto the refactored QMP driver. > > > > I also have patches for QMP to make it switch USB/DP combo -> 4-lane > > > > DP, which allow 4k support without DSC, unfortunately switch back to > > > > USB has not been fully reliable, so this requires some more work > > > > (downstream involves DWC3 here as well, to reprogram the PHY). > > > > > > Oki doki that makes sense and is pretty much in-line with what I thought. > > > > > > We still have a bunch of typec-mux and phy work to do even with adsp/glink > > > doing the TCPM. > > > > > > > Correct, the registration of QMP as a typec_switch and typec_mux and > > handling of respective notification remains open and should (by design) > > be independent of the TCPM implementation. > > > > In particular the orientation switching is an itch worth scratching at > > this time. But when the DPU becomes capable of producing 4k@60 output it > > would obviously be nice to have the whole shebang :) > > Did you try it with the wide planes patchset at [1]? I was able to get > stable 4k@30 on RB3 (being limited only by the DSI-HDMI bridge). > > [1] https://lore.kernel.org/linux-arm-msm/20221229191856.3508092-1-dmitry.baryshkov@linaro.org/ > I have not done so, as the patches I had for switching to 4-lane DP output needs to be rewritten since the refactoring. I had no problem doing 4k@30 prior to your efforts, so I'm interested in validating the changes you've made. Thanks, Bjorn > -- > With best wishes > Dmitry > ^ permalink raw reply [flat|nested] 19+ messages in thread
end of thread, other threads:[~2023-01-20 10:33 UTC | newest] Thread overview: 19+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2023-01-13 4:11 [PATCH v2 0/4] soc: qcom: Introduce PMIC GLINK Bjorn Andersson 2023-01-13 4:11 ` [PATCH v2 1/4] dt-bindings: soc: qcom: Introduce PMIC GLINK binding Bjorn Andersson 2023-01-17 18:00 ` Rob Herring 2023-01-18 18:12 ` Bjorn Andersson 2023-01-13 4:11 ` [PATCH v2 2/4] soc: qcom: pmic_glink: Introduce base PMIC GLINK driver Bjorn Andersson 2023-01-13 4:11 ` [PATCH v2 3/4] soc: qcom: pmic_glink: Introduce altmode support Bjorn Andersson 2023-01-15 19:10 ` Steev Klimaszewski 2023-01-17 2:08 ` Bjorn Andersson 2023-01-20 10:06 ` Neil Armstrong 2023-01-20 10:33 ` Neil Armstrong 2023-01-13 4:11 ` [PATCH v2 4/4] power: supply: Introduce Qualcomm PMIC GLINK power supply Bjorn Andersson 2023-01-13 14:56 ` [PATCH v2 0/4] soc: qcom: Introduce PMIC GLINK Konrad Dybcio 2023-01-17 2:13 ` Bjorn Andersson 2023-01-13 17:10 ` Bryan O'Donoghue 2023-01-17 2:32 ` Bjorn Andersson 2023-01-17 2:37 ` Bryan O'Donoghue 2023-01-17 2:58 ` Bjorn Andersson 2023-01-17 9:26 ` Dmitry Baryshkov 2023-01-17 15:48 ` Bjorn Andersson
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).