diff --git a/Documentation/devicetree/bindings/connector/pcie-m2-e-connector.yaml b/Documentation/devicetree/bindings/connector/pcie-m2-e-connector.yaml new file mode 100644 index 0000000000000..f7859aa9b6346 --- /dev/null +++ b/Documentation/devicetree/bindings/connector/pcie-m2-e-connector.yaml @@ -0,0 +1,184 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/connector/pcie-m2-e-connector.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: PCIe M.2 Mechanical Key E Connector + +maintainers: + - Manivannan Sadhasivam + +description: + A PCIe M.2 E connector node represents a physical PCIe M.2 Mechanical Key E + connector. Mechanical Key E connectors are used to connect Wireless + Connectivity devices including combinations of Wi-Fi, BT, NFC to the host + machine over interfaces like PCIe/SDIO, USB/UART+PCM, and I2C. + +properties: + compatible: + const: pcie-m2-e-connector + + vpcie3v3-supply: + description: A phandle to the regulator for 3.3v supply. + + vpcie1v8-supply: + description: A phandle to the regulator for VIO 1.8v supply. + + i2c-parent: + $ref: /schemas/types.yaml#/definitions/phandle + description: I2C interface + + clocks: + description: 32.768 KHz Suspend Clock (SUSCLK) input from the host system to + the M.2 card. Refer, PCI Express M.2 Specification r4.0, sec 3.1.12.1 for + more details. + maxItems: 1 + + w-disable1-gpios: + description: GPIO output to W_DISABLE1# signal. This signal is used by the + host system to disable WiFi radio in the M.2 card. Refer, PCI Express M.2 + Specification r4.0, sec 3.1.12.3 for more details. + maxItems: 1 + + w-disable2-gpios: + description: GPIO output to W_DISABLE2# signal. This signal is used by the + host system to disable BT radio in the M.2 card. Refer, PCI Express M.2 + Specification r4.0, sec 3.1.12.3 for more details. + maxItems: 1 + + viocfg-gpios: + description: GPIO input to IO voltage configuration (VIO_CFG) signal. The + card drives this signal to indicate to the host system whether the card + supports an independent IO voltage domain for sideband signals. Refer, + PCI Express M.2 Specification r4.0, sec 3.1.15.1 for more details. + maxItems: 1 + + uart-wake-gpios: + description: GPIO input to UART_WAKE# signal. The card asserts this signal + to wake the host system and initiate UART interface communication. Refer, + PCI Express M.2 Specification r4.0, sec 3.1.8.1 for more details. + maxItems: 1 + + sdio-wake-gpios: + description: GPIO input to SDIO_WAKE# signal. The card asserts this signal + to wake the host system and initiate SDIO interface communication. Refer, + PCI Express M.2 Specification r4.0, sec 3.1.7 for more details. + maxItems: 1 + + sdio-reset-gpios: + description: GPIO output to SDIO_RESET# signal. This signal is used by the + host system to reset SDIO interface of the M.2 card. Refer, PCI Express + M.2 Specification r4.0, sec 3.1.7 for more details. + maxItems: 1 + + vendor-porta-gpios: + description: GPIO for the first vendor specific signal (VENDOR_PORTA). This + signal's functionality is defined by the card manufacturer and may be + used for proprietary features. Refer the card vendor's documentation for + details. + maxItems: 1 + + vendor-portb-gpios: + description: GPIO for the second vendor specific signal (VENDOR_PORTB). This + signal's functionality is defined by the card manufacturer and may be + used for proprietary features. Refer the card vendor's documentation for + details. + maxItems: 1 + + vendor-portc-gpios: + description: GPIO for the third vendor specific signal (VENDOR_PORTC). This + signal's functionality is defined by the card manufacturer and may be + used for proprietary features. Refer the card vendor's documentation for + details. + maxItems: 1 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + description: OF graph bindings modeling the interfaces exposed on the + connector. Since a single connector can have multiple interfaces, every + interface has an assigned OF graph port number as described below. + + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + description: PCIe interface for Wi-Fi + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: SDIO interface for Wi-Fi + + port@2: + $ref: /schemas/graph.yaml#/properties/port + description: USB 2.0 interface for BT + + port@3: + $ref: /schemas/graph.yaml#/properties/port + description: UART interface for BT + + port@4: + $ref: /schemas/graph.yaml#/properties/port + description: PCM/I2S interface + + anyOf: + - anyOf: + - required: + - port@0 + - required: + - port@1 + - anyOf: + - required: + - port@2 + - required: + - port@3 + +required: + - compatible + - vpcie3v3-supply + +additionalProperties: false + +examples: + # PCI M.2 Key E connector for Wi-Fi/BT with PCIe/UART interfaces + - | + #include + + connector { + compatible = "pcie-m2-e-connector"; + vpcie3v3-supply = <&vreg_wcn_3p3>; + vpcie1v8-supply = <&vreg_l15b_1p8>; + i2c-parent = <&i2c0>; + w-disable1-gpios = <&tlmm 115 GPIO_ACTIVE_LOW>; + w-disable2-gpios = <&tlmm 116 GPIO_ACTIVE_LOW>; + viocfg-gpios = <&tlmm 117 GPIO_ACTIVE_HIGH>; + uart-wake-gpios = <&tlmm 118 GPIO_ACTIVE_LOW>; + sdio-wake-gpios = <&tlmm 119 GPIO_ACTIVE_LOW>; + sdio-reset-gpios = <&tlmm 120 GPIO_ACTIVE_LOW>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + #address-cells = <1>; + #size-cells = <0>; + + endpoint@0 { + reg = <0>; + remote-endpoint = <&pcie4_port0_ep>; + }; + }; + + port@3 { + reg = <3>; + #address-cells = <1>; + #size-cells = <0>; + + endpoint@0 { + reg = <0>; + remote-endpoint = <&uart14_ep>; + }; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/connector/pcie-m2-m-connector.yaml b/Documentation/devicetree/bindings/connector/pcie-m2-m-connector.yaml new file mode 100644 index 0000000000000..36a99a3b39d7a --- /dev/null +++ b/Documentation/devicetree/bindings/connector/pcie-m2-m-connector.yaml @@ -0,0 +1,145 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/connector/pcie-m2-m-connector.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: PCIe M.2 Mechanical Key M Connector + +maintainers: + - Manivannan Sadhasivam + +description: + A PCIe M.2 M connector node represents a physical PCIe M.2 Mechanical Key M + connector. The Mechanical Key M connectors are used to connect SSDs to the + host system over PCIe/SATA interfaces. These connectors also offer optional + interfaces like USB, SMBus. + +properties: + compatible: + const: pcie-m2-m-connector + + vpcie3v3-supply: + description: A phandle to the regulator for 3.3v supply. + + vpcie1v8-supply: + description: A phandle to the regulator for VIO 1.8v supply. + + ports: + $ref: /schemas/graph.yaml#/properties/ports + description: OF graph bindings modeling the interfaces exposed on the + connector. Since a single connector can have multiple interfaces, every + interface has an assigned OF graph port number as described below. + + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + description: PCIe interface + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: SATA interface + + port@2: + $ref: /schemas/graph.yaml#/properties/port + description: USB 2.0 interface + + anyOf: + - required: + - port@0 + - required: + - port@1 + + i2c-parent: + $ref: /schemas/types.yaml#/definitions/phandle + description: I2C interface + + clocks: + description: 32.768 KHz Suspend Clock (SUSCLK) input from the host system to + the M.2 card. Refer, PCI Express M.2 Specification r4.0, sec 3.1.12.1 for + more details. + maxItems: 1 + + pedet-gpios: + description: GPIO input to PEDET signal. This signal is used by the host + systems to determine the communication protocol that the M.2 card uses; + SATA signaling (low) or PCIe signaling (high). Refer, PCI Express M.2 + Specification r4.0, sec 3.3.4.2 for more details. + maxItems: 1 + + viocfg-gpios: + description: GPIO input to IO voltage configuration (VIO_CFG) signal. This + signal is used by the host systems to determine whether the card supports + an independent IO voltage domain for the sideband signals or not. Refer, + PCI Express M.2 Specification r4.0, sec 3.1.15.1 for more details. + maxItems: 1 + + pwrdis-gpios: + description: GPIO output to Power Disable (PWRDIS) signal. This signal is + used by the host system to disable power on the M.2 card. Refer, PCI + Express M.2 Specification r4.0, sec 3.3.5.2 for more details. + maxItems: 1 + + pln-gpios: + description: GPIO output to Power Loss Notification (PLN#) signal. This + signal is used by the host system to notify the M.2 card that the power + loss event is about to occur. Refer, PCI Express M.2 Specification r4.0, + sec 3.2.17.1 for more details. + maxItems: 1 + + plas3-gpios: + description: GPIO input to Power Loss Acknowledge (PLA_S3#) signal. This + signal is used by the host system to receive the acknowledgment of the M.2 + card's preparation for power loss. + maxItems: 1 + +required: + - compatible + - vpcie3v3-supply + +additionalProperties: false + +examples: + # PCI M.2 Key M connector for SSDs with PCIe interface + - | + #include + + connector { + compatible = "pcie-m2-m-connector"; + vpcie3v3-supply = <&vreg_nvme>; + i2c-parent = <&i2c0>; + pedet-gpios = <&tlmm 95 GPIO_ACTIVE_HIGH>; + viocfg-gpios = <&tlmm 96 GPIO_ACTIVE_HIGH>; + pwrdis-gpios = <&tlmm 97 GPIO_ACTIVE_HIGH>; + pln-gpios = <&tlmm 98 GPIO_ACTIVE_LOW>; + plas3-gpios = <&tlmm 99 GPIO_ACTIVE_LOW>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + #address-cells = <1>; + #size-cells = <0>; + + reg = <0>; + + endpoint@0 { + reg = <0>; + remote-endpoint = <&pcie6_port0_ep>; + }; + }; + + port@2 { + #address-cells = <1>; + #size-cells = <0>; + + reg = <2>; + + endpoint@0 { + reg = <0>; + remote-endpoint = <&usb_hs_ep>; + }; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/serial/serial.yaml b/Documentation/devicetree/bindings/serial/serial.yaml index 6aa9cfae417b8..96eb1de8771e9 100644 --- a/Documentation/devicetree/bindings/serial/serial.yaml +++ b/Documentation/devicetree/bindings/serial/serial.yaml @@ -87,6 +87,9 @@ properties: description: TX FIFO threshold configuration (in bytes). + port: + $ref: /schemas/graph.yaml#/properties/port + patternProperties: "^(bluetooth|bluetooth-gnss|embedded-controller|gnss|gps|mcu|onewire)$": if: diff --git a/MAINTAINERS b/MAINTAINERS index 09acdeab109c8..bab3f628d653e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20529,6 +20529,14 @@ F: Documentation/driver-api/pwrseq.rst F: drivers/power/sequencing/ F: include/linux/pwrseq/ +PCIE M.2 POWER SEQUENCING +M: Manivannan Sadhasivam +L: linux-pci@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/connector/pcie-m2-e-connector.yaml +F: Documentation/devicetree/bindings/connector/pcie-m2-m-connector.yaml +F: drivers/power/sequencing/pwrseq-pcie-m2.c + POWER STATE COORDINATION INTERFACE (PSCI) M: Mark Rutland M: Lorenzo Pieralisi diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index f616dd6ae116e..5e6405e8da6da 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -252,6 +252,7 @@ CONFIG_PCI_ENDPOINT=y CONFIG_PCI_ENDPOINT_CONFIGFS=y CONFIG_PCI_EPF_TEST=m CONFIG_PCI_PWRCTRL_GENERIC=m +CONFIG_POWER_SEQUENCING_PCIE_M2=m CONFIG_DEVTMPFS=y CONFIG_DEVTMPFS_MOUNT=y CONFIG_FW_LOADER_USER_HELPER=y diff --git a/drivers/bluetooth/hci_qca.c b/drivers/bluetooth/hci_qca.c index 5d1730e58e47b..520852f7066d7 100644 --- a/drivers/bluetooth/hci_qca.c +++ b/drivers/bluetooth/hci_qca.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -2394,7 +2395,7 @@ static int qca_serdev_probe(struct serdev_device *serdev) struct hci_dev *hdev; const struct qca_device_data *data; int err; - bool power_ctrl_enabled = true; + bool bt_en_available = true; qcadev = devm_kzalloc(&serdev->dev, sizeof(*qcadev), GFP_KERNEL); if (!qcadev) @@ -2444,6 +2445,25 @@ static int qca_serdev_probe(struct serdev_device *serdev) case QCA_WCN6855: case QCA_WCN7850: case QCA_WCN6750: + /* + * OF graph link is only present for BT devices attached through + * the M.2 Key E connector. + */ + if (of_graph_is_present(dev_of_node(&serdev->ctrl->dev))) { + struct device *dev; + + qcadev->bt_power->pwrseq = devm_pwrseq_get(&serdev->ctrl->dev, + "uart"); + if (IS_ERR(qcadev->bt_power->pwrseq)) + return PTR_ERR(qcadev->bt_power->pwrseq); + + dev = pwrseq_to_device(qcadev->bt_power->pwrseq); + if (!device_property_present(dev, "w-disable2-gpios")) + bt_en_available = false; + + break; + } + if (!device_property_present(&serdev->dev, "enable-gpios")) { /* * Backward compatibility with old DT sources. If the @@ -2493,7 +2513,7 @@ static int qca_serdev_probe(struct serdev_device *serdev) data->soc_type == QCA_WCN6855 || data->soc_type == QCA_QCC2072 || data->soc_type == QCA_WCN7850)) - power_ctrl_enabled = false; + bt_en_available = false; qcadev->sw_ctrl = devm_gpiod_get_optional(&serdev->dev, "swctrl", GPIOD_IN); @@ -2532,7 +2552,7 @@ static int qca_serdev_probe(struct serdev_device *serdev) } if (!qcadev->bt_en) - power_ctrl_enabled = false; + bt_en_available = false; qcadev->susclk = devm_clk_get_optional_enabled_with_rate( &serdev->dev, NULL, SUSCLK_RATE_32KHZ); @@ -2550,7 +2570,7 @@ static int qca_serdev_probe(struct serdev_device *serdev) hdev = qcadev->serdev_hu.hdev; - if (power_ctrl_enabled) { + if (bt_en_available) { hci_set_quirk(hdev, HCI_QUIRK_NON_PERSISTENT_SETUP); hdev->shutdown = qca_power_off; } diff --git a/drivers/pci/pwrctrl/Kconfig b/drivers/pci/pwrctrl/Kconfig index 0a93ac4cd11b5..811338ceea071 100644 --- a/drivers/pci/pwrctrl/Kconfig +++ b/drivers/pci/pwrctrl/Kconfig @@ -13,6 +13,7 @@ config PCI_PWRCTRL_PWRSEQ config PCI_PWRCTRL_GENERIC tristate "Generic PCI Power Control driver for PCI slots" + select POWER_SEQUENCING select PCI_PWRCTRL help Say Y here to enable the generic PCI Power Control driver to control diff --git a/drivers/pci/pwrctrl/core.c b/drivers/pci/pwrctrl/core.c index 47a8771257615..09777de3ae29f 100644 --- a/drivers/pci/pwrctrl/core.c +++ b/drivers/pci/pwrctrl/core.c @@ -344,10 +344,10 @@ static int pci_pwrctrl_create_device(struct device_node *np, /* * Check whether the pwrctrl device really needs to be created or not. - * This is decided based on at least one of the power supplies being - * defined in the devicetree node of the device. + * This is decided based on at least one of the power supplies defined + * in the devicetree node of the device or the graph property. */ - if (!of_pci_supply_present(np)) { + if (!of_pci_supply_present(np) && !of_graph_is_present(np)) { dev_dbg(parent, "Skipping OF node: %s\n", np->name); return 0; } diff --git a/drivers/pci/pwrctrl/generic.c b/drivers/pci/pwrctrl/generic.c index e29b7bfdd6863..555fd866800ae 100644 --- a/drivers/pci/pwrctrl/generic.c +++ b/drivers/pci/pwrctrl/generic.c @@ -8,8 +8,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -18,6 +20,7 @@ struct pci_pwrctrl_slot { struct regulator_bulk_data *supplies; int num_supplies; struct clk *clk; + struct pwrseq_desc *pwrseq; }; static int pci_pwrctrl_slot_power_on(struct pci_pwrctrl *pwrctrl) @@ -26,6 +29,11 @@ static int pci_pwrctrl_slot_power_on(struct pci_pwrctrl *pwrctrl) struct pci_pwrctrl_slot, pwrctrl); int ret; + if (slot->pwrseq) { + pwrseq_power_on(slot->pwrseq); + return 0; + } + ret = regulator_bulk_enable(slot->num_supplies, slot->supplies); if (ret < 0) { dev_err(slot->pwrctrl.dev, "Failed to enable slot regulators\n"); @@ -40,6 +48,11 @@ static int pci_pwrctrl_slot_power_off(struct pci_pwrctrl *pwrctrl) struct pci_pwrctrl_slot *slot = container_of(pwrctrl, struct pci_pwrctrl_slot, pwrctrl); + if (slot->pwrseq) { + pwrseq_power_off(slot->pwrseq); + return 0; + } + regulator_bulk_disable(slot->num_supplies, slot->supplies); clk_disable_unprepare(slot->clk); @@ -64,6 +77,15 @@ static int pci_pwrctrl_slot_probe(struct platform_device *pdev) if (!slot) return -ENOMEM; + if (of_graph_is_present(dev_of_node(dev))) { + slot->pwrseq = devm_pwrseq_get(dev, "pcie"); + if (IS_ERR(slot->pwrseq)) + return dev_err_probe(dev, PTR_ERR(slot->pwrseq), + "Failed to get the power sequencer\n"); + + goto skip_resources; + } + ret = of_regulator_bulk_get_all(dev, dev_of_node(dev), &slot->supplies); if (ret < 0) @@ -71,19 +93,19 @@ static int pci_pwrctrl_slot_probe(struct platform_device *pdev) slot->num_supplies = ret; - ret = devm_add_action_or_reset(dev, devm_pci_pwrctrl_slot_release, - slot); - if (ret) - return ret; - slot->clk = devm_clk_get_optional_enabled(dev, NULL); if (IS_ERR(slot->clk)) return dev_err_probe(dev, PTR_ERR(slot->clk), "Failed to enable slot clock\n"); +skip_resources: slot->pwrctrl.power_on = pci_pwrctrl_slot_power_on; slot->pwrctrl.power_off = pci_pwrctrl_slot_power_off; + ret = devm_add_action_or_reset(dev, devm_pci_pwrctrl_slot_release, slot); + if (ret) + return ret; + pci_pwrctrl_init(&slot->pwrctrl, dev); ret = devm_pci_pwrctrl_device_set_ready(dev, &slot->pwrctrl); diff --git a/drivers/power/sequencing/Kconfig b/drivers/power/sequencing/Kconfig index 280f92beb5d0e..1c5f5820f5b76 100644 --- a/drivers/power/sequencing/Kconfig +++ b/drivers/power/sequencing/Kconfig @@ -35,4 +35,15 @@ config POWER_SEQUENCING_TH1520_GPU GPU. This driver handles the complex clock and reset sequence required to power on the Imagination BXM GPU on this platform. +config POWER_SEQUENCING_PCIE_M2 + tristate "PCIe M.2 connector power sequencing driver" + depends on OF + depends on PCI + depends on SERIAL_DEV_BUS + select OF_DYNAMIC + help + Say Y here to enable the power sequencing driver for PCIe M.2 + connectors. This driver handles the power sequencing for the M.2 + connectors exposing multiple interfaces like PCIe, SATA, UART, etc... + endif diff --git a/drivers/power/sequencing/Makefile b/drivers/power/sequencing/Makefile index 96c1cf0a98ac5..0911d46182989 100644 --- a/drivers/power/sequencing/Makefile +++ b/drivers/power/sequencing/Makefile @@ -5,3 +5,4 @@ pwrseq-core-y := core.o obj-$(CONFIG_POWER_SEQUENCING_QCOM_WCN) += pwrseq-qcom-wcn.o obj-$(CONFIG_POWER_SEQUENCING_TH1520_GPU) += pwrseq-thead-gpu.o +obj-$(CONFIG_POWER_SEQUENCING_PCIE_M2) += pwrseq-pcie-m2.o diff --git a/drivers/power/sequencing/core.c b/drivers/power/sequencing/core.c index 1fcf0af7cc0bb..4f74464b7ff8a 100644 --- a/drivers/power/sequencing/core.c +++ b/drivers/power/sequencing/core.c @@ -966,6 +966,15 @@ int pwrseq_power_off(struct pwrseq_desc *desc) } EXPORT_SYMBOL_GPL(pwrseq_power_off); +struct device *pwrseq_to_device(struct pwrseq_desc *desc) +{ + if (!desc) + return NULL; + + return &desc->pwrseq->dev; +} +EXPORT_SYMBOL_GPL(pwrseq_to_device); + #if IS_ENABLED(CONFIG_DEBUG_FS) struct pwrseq_debugfs_count_ctx { diff --git a/drivers/power/sequencing/pwrseq-pcie-m2.c b/drivers/power/sequencing/pwrseq-pcie-m2.c new file mode 100644 index 0000000000000..a80489eab34d5 --- /dev/null +++ b/drivers/power/sequencing/pwrseq-pcie-m2.c @@ -0,0 +1,604 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * Author: Manivannan Sadhasivam + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct pwrseq_pci_dev { + struct serdev_device *serdev; + struct of_changeset *ocs; + struct pci_dev *pdev; + struct list_head list; +}; + +struct pwrseq_pcie_m2_pdata { + const struct pwrseq_target_data **targets; +}; + +struct pwrseq_pcie_m2_ctx { + struct pwrseq_device *pwrseq; + struct device_node *of_node; + const struct pwrseq_pcie_m2_pdata *pdata; + struct regulator_bulk_data *regs; + size_t num_vregs; + struct notifier_block nb; + struct gpio_desc *w_disable1_gpio; + struct gpio_desc *w_disable2_gpio; + struct device *dev; + struct list_head pci_devices; + struct mutex list_lock; +}; + +static int pwrseq_pcie_m2_vregs_enable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + return regulator_bulk_enable(ctx->num_vregs, ctx->regs); +} + +static int pwrseq_pcie_m2_vregs_disable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + return regulator_bulk_disable(ctx->num_vregs, ctx->regs); +} + +static const struct pwrseq_unit_data pwrseq_pcie_m2_vregs_unit_data = { + .name = "regulators-enable", + .enable = pwrseq_pcie_m2_vregs_enable, + .disable = pwrseq_pcie_m2_vregs_disable, +}; + +static const struct pwrseq_unit_data *pwrseq_pcie_m2_unit_deps[] = { + &pwrseq_pcie_m2_vregs_unit_data, + NULL +}; + +static int pwrseq_pci_m2_e_uart_enable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + return gpiod_set_value_cansleep(ctx->w_disable2_gpio, 0); +} + +static int pwrseq_pci_m2_e_uart_disable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + return gpiod_set_value_cansleep(ctx->w_disable2_gpio, 1); +} + +static const struct pwrseq_unit_data pwrseq_pcie_m2_e_uart_unit_data = { + .name = "uart-enable", + .deps = pwrseq_pcie_m2_unit_deps, + .enable = pwrseq_pci_m2_e_uart_enable, + .disable = pwrseq_pci_m2_e_uart_disable, +}; + +static int pwrseq_pci_m2_e_pcie_enable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + return gpiod_set_value_cansleep(ctx->w_disable1_gpio, 0); +} + +static int pwrseq_pci_m2_e_pcie_disable(struct pwrseq_device *pwrseq) +{ + struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + + return gpiod_set_value_cansleep(ctx->w_disable1_gpio, 1); +} + +static const struct pwrseq_unit_data pwrseq_pcie_m2_e_pcie_unit_data = { + .name = "pcie-enable", + .deps = pwrseq_pcie_m2_unit_deps, + .enable = pwrseq_pci_m2_e_pcie_enable, + .disable = pwrseq_pci_m2_e_pcie_disable, +}; + +static const struct pwrseq_unit_data pwrseq_pcie_m2_m_pcie_unit_data = { + .name = "pcie-enable", + .deps = pwrseq_pcie_m2_unit_deps, +}; + +static int pwrseq_pcie_m2_e_pwup_delay(struct pwrseq_device *pwrseq) +{ + /* + * FIXME: This delay is only required for some Qcom WLAN/BT cards like + * WCN7850 and not for all devices. But currently, there is no way to + * identify the device model before enumeration. + */ + msleep(50); + + return 0; +} + +static const struct pwrseq_target_data pwrseq_pcie_m2_e_uart_target_data = { + .name = "uart", + .unit = &pwrseq_pcie_m2_e_uart_unit_data, + .post_enable = pwrseq_pcie_m2_e_pwup_delay, +}; + +static const struct pwrseq_target_data pwrseq_pcie_m2_e_pcie_target_data = { + .name = "pcie", + .unit = &pwrseq_pcie_m2_e_pcie_unit_data, + .post_enable = pwrseq_pcie_m2_e_pwup_delay, +}; + +static const struct pwrseq_target_data pwrseq_pcie_m2_m_pcie_target_data = { + .name = "pcie", + .unit = &pwrseq_pcie_m2_m_pcie_unit_data, +}; + +static const struct pwrseq_target_data *pwrseq_pcie_m2_e_targets[] = { + &pwrseq_pcie_m2_e_pcie_target_data, + &pwrseq_pcie_m2_e_uart_target_data, + NULL +}; + +static const struct pwrseq_target_data *pwrseq_pcie_m2_m_targets[] = { + &pwrseq_pcie_m2_m_pcie_target_data, + NULL +}; + +static const struct pwrseq_pcie_m2_pdata pwrseq_pcie_m2_e_of_data = { + .targets = pwrseq_pcie_m2_e_targets, +}; + +static const struct pwrseq_pcie_m2_pdata pwrseq_pcie_m2_m_of_data = { + .targets = pwrseq_pcie_m2_m_targets, +}; + +static int pwrseq_pcie_m2_match(struct pwrseq_device *pwrseq, + struct device *dev) +{ + struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); + struct device_node *endpoint __free(device_node) = NULL; + + /* + * Traverse the 'remote-endpoint' nodes and check if the remote node's + * parent matches the OF node of 'dev'. + */ + for_each_endpoint_of_node(ctx->of_node, endpoint) { + struct device_node *remote __free(device_node) = + of_graph_get_remote_port_parent(endpoint); + if (remote && (remote == dev_of_node(dev))) + return PWRSEQ_MATCH_OK; + } + + return PWRSEQ_NO_MATCH; +} + +static const struct pci_device_id pwrseq_m2_pci_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_QCOM, 0x1107), + .driver_data = (kernel_ulong_t)"qcom,wcn7850-bt" }, + { } /* Sentinel */ +}; + +static int pwrseq_pcie_m2_create_bt_node(struct pwrseq_pcie_m2_ctx *ctx, + struct pwrseq_pci_dev *pci_dev, + struct device_node *parent, + struct pci_dev *pdev) +{ + const struct pci_device_id *id; + struct device *dev = ctx->dev; + const char *compatible; + struct device_node *np; + int ret; + + id = pci_match_id(pwrseq_m2_pci_ids, pdev); + if (WARN_ON_ONCE(!id)) /* Shouldn't happen */ + return -ENODEV; + + compatible = (const char *)id->driver_data; + + pci_dev->ocs = kzalloc(sizeof(*pci_dev->ocs), GFP_KERNEL); + if (!pci_dev->ocs) + return -ENOMEM; + + of_changeset_init(pci_dev->ocs); + + np = of_changeset_create_node(pci_dev->ocs, parent, "bluetooth"); + if (!np) { + dev_err(dev, "Failed to create bluetooth node\n"); + ret = -ENODEV; + goto err_destroy_changeset; + } + + ret = of_changeset_add_prop_string(pci_dev->ocs, np, "compatible", compatible); + if (ret) { + dev_err(dev, "Failed to add bluetooth compatible: %d\n", ret); + goto err_destroy_changeset; + } + + ret = of_changeset_apply(pci_dev->ocs); + if (ret) { + dev_err(dev, "Failed to apply changeset: %d\n", ret); + goto err_destroy_changeset; + } + + ret = device_add_of_node(&pci_dev->serdev->dev, np); + if (ret) { + dev_err(dev, "Failed to add OF node: %d\n", ret); + goto err_revert_changeset; + } + + return 0; + +err_revert_changeset: + of_changeset_revert(pci_dev->ocs); +err_destroy_changeset: + of_changeset_destroy(pci_dev->ocs); + kfree(pci_dev->ocs); + pci_dev->ocs = NULL; + + return ret; +} + +static int pwrseq_pcie_m2_create_serdev_one(struct pwrseq_pcie_m2_ctx *ctx, + struct pci_dev *pdev) +{ + struct serdev_controller *serdev_ctrl; + struct device *dev = ctx->dev; + struct pwrseq_pci_dev *pci_dev; + int ret; + + struct device_node *serdev_parent __free(device_node) = + of_graph_get_remote_node(dev_of_node(ctx->dev), 3, 0); + if (!serdev_parent) + return 0; + + serdev_ctrl = of_find_serdev_controller_by_node(serdev_parent); + if (!serdev_ctrl) + return 0; + + /* Bail out if the device was already attached to this controller */ + if (serdev_ctrl->serdev) { + serdev_controller_put(serdev_ctrl); + return 0; + } + + /* Bail out if the serdev device was already created for the PCI dev */ + scoped_guard(mutex, &ctx->list_lock) { + list_for_each_entry(pci_dev, &ctx->pci_devices, list) { + if (pci_dev->pdev == pdev) + return 0; + } + } + + pci_dev = kzalloc(sizeof(*pci_dev), GFP_KERNEL); + if (!pci_dev) { + ret = -ENOMEM; + goto err_put_ctrl; + } + + pci_dev->serdev = serdev_device_alloc(serdev_ctrl); + if (!pci_dev->serdev) { + ret = -ENOMEM; + goto err_free_pci_dev; + } + + ret = pwrseq_pcie_m2_create_bt_node(ctx, pci_dev, serdev_parent, pdev); + if (ret) + goto err_free_serdev; + + ret = serdev_device_add(pci_dev->serdev); + if (ret) { + dev_err(dev, "Failed to add serdev for PCI device (%s): %d\n", + pci_name(pdev), ret); + goto err_free_dt_node; + } + + serdev_controller_put(serdev_ctrl); + + pci_dev->pdev = pci_dev_get(pdev); + + mutex_lock(&ctx->list_lock); + list_add_tail(&pci_dev->list, &ctx->pci_devices); + mutex_unlock(&ctx->list_lock); + + return 0; + +err_free_dt_node: + device_remove_of_node(&pci_dev->serdev->dev); + of_changeset_revert(pci_dev->ocs); + of_changeset_destroy(pci_dev->ocs); + kfree(pci_dev->ocs); + pci_dev->ocs = NULL; +err_free_serdev: + serdev_device_put(pci_dev->serdev); + pci_dev->serdev = NULL; +err_free_pci_dev: + kfree(pci_dev); +err_put_ctrl: + serdev_controller_put(serdev_ctrl); + + return ret; +} + +static void __pwrseq_pcie_m2_remove_serdev(struct pwrseq_pcie_m2_ctx *ctx, + struct pwrseq_pci_dev *pci_dev) +{ + if (pci_dev->serdev) { + device_remove_of_node(&pci_dev->serdev->dev); + serdev_device_remove(pci_dev->serdev); + } + + if (pci_dev->ocs) { + of_changeset_revert(pci_dev->ocs); + of_changeset_destroy(pci_dev->ocs); + kfree(pci_dev->ocs); + } + + pci_dev_put(pci_dev->pdev); + list_del(&pci_dev->list); + kfree(pci_dev); +} + +static void pwrseq_pcie_m2_remove_serdev(struct pwrseq_pcie_m2_ctx *ctx, + struct pci_dev *pdev) +{ + struct pwrseq_pci_dev *pci_dev, *tmp; + + mutex_lock(&ctx->list_lock); + list_for_each_entry_safe(pci_dev, tmp, &ctx->pci_devices, list) { + if (!pdev || pci_dev->pdev == pdev) { + __pwrseq_pcie_m2_remove_serdev(ctx, pci_dev); + if (pdev) + break; + } + } + mutex_unlock(&ctx->list_lock); +} + +static int pwrseq_pcie_m2_notify(struct notifier_block *nb, unsigned long action, + void *data) +{ + struct pwrseq_pcie_m2_ctx *ctx = container_of(nb, struct pwrseq_pcie_m2_ctx, nb); + struct pci_dev *pdev = to_pci_dev(data); + int ret; + + /* + * Check whether the PCI device is associated with this M.2 connector or + * not, by comparing the OF node of the PCI device parent and the Port 0 + * (PCIe) remote node parent OF node. + */ + struct device_node *pci_parent __free(device_node) = + of_graph_get_remote_node(dev_of_node(ctx->dev), 0, 0); + if (!pci_parent || (pci_parent != pdev->dev.parent->of_node)) + return NOTIFY_DONE; + + switch (action) { + case BUS_NOTIFY_ADD_DEVICE: + if (pci_match_id(pwrseq_m2_pci_ids, pdev)) { + ret = pwrseq_pcie_m2_create_serdev_one(ctx, pdev); + if (ret) + return notifier_from_errno(ret); + } + break; + case BUS_NOTIFY_REMOVED_DEVICE: + if (pci_match_id(pwrseq_m2_pci_ids, pdev)) + pwrseq_pcie_m2_remove_serdev(ctx, pdev); + + break; + } + + return NOTIFY_OK; +} + +static bool pwrseq_pcie_m2_check_remote_node(struct device *dev, u8 port, u8 endpoint, + const char *node) +{ + struct device_node *remote __free(device_node) = + of_graph_get_remote_node(dev_of_node(dev), port, endpoint); + + if (remote && of_node_name_eq(remote, node)) + return true; + + return false; +} + +/* + * If the connector exposes a non-discoverable bus like UART, the respective + * protocol device needs to be created manually with the help of the notifier + * of the discoverable bus like PCIe. + */ +static int pwrseq_pcie_m2_register_notifier(struct pwrseq_pcie_m2_ctx *ctx) +{ + int ret; + + /* + * Register a PCI notifier for Key E connector that has PCIe as Port + * 0/Endpoint 0 interface and Serial as Port 3/Endpoint 0 interface. + */ + if (!pwrseq_pcie_m2_check_remote_node(ctx->dev, 3, 0, "serial") || + !pwrseq_pcie_m2_check_remote_node(ctx->dev, 0, 0, "pcie")) + return 0; + + ctx->nb.notifier_call = pwrseq_pcie_m2_notify; + ret = bus_register_notifier(&pci_bus_type, &ctx->nb); + if (ret) + return dev_err_probe(ctx->dev, ret, + "Failed to register notifier for serdev\n"); + return 0; +} + +static int pwrseq_pcie_m2_create_serdev(struct pwrseq_pcie_m2_ctx *ctx) +{ + struct pci_dev *pdev = NULL; + int ret; + + if (!pwrseq_pcie_m2_check_remote_node(ctx->dev, 3, 0, "serial") || + !pwrseq_pcie_m2_check_remote_node(ctx->dev, 0, 0, "pcie")) + return 0; + + struct device_node *pci_parent __free(device_node) = + of_graph_get_remote_node(dev_of_node(ctx->dev), 0, 0); + if (!pci_parent) + return 0; + + /* Create serdev for existing PCI devices if required */ + for_each_pci_dev(pdev) { + if (!pdev->dev.parent || pci_parent != pdev->dev.parent->of_node) + continue; + + if (!pci_match_id(pwrseq_m2_pci_ids, pdev)) + continue; + + ret = pwrseq_pcie_m2_create_serdev_one(ctx, pdev); + if (ret) { + dev_err_probe(ctx->dev, ret, + "Failed to create serdev for PCI device (%s)\n", + pci_name(pdev)); + pci_dev_put(pdev); + goto err_remove_serdev; + } + } + + return 0; + +err_remove_serdev: + pwrseq_pcie_m2_remove_serdev(ctx, NULL); + + return ret; +} + +static int pwrseq_pcie_m2_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pwrseq_pcie_m2_ctx *ctx; + struct pwrseq_config config = {}; + int ret; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + platform_set_drvdata(pdev, ctx); + ctx->of_node = dev_of_node(dev); + ctx->pdata = device_get_match_data(dev); + if (!ctx->pdata) + return dev_err_probe(dev, -ENODEV, + "Failed to obtain platform data\n"); + + /* + * Currently, of_regulator_bulk_get_all() is the only regulator API that + * allows to get all supplies in the devicetree node without manually + * specifying them. + */ + ret = of_regulator_bulk_get_all(dev, dev_of_node(dev), &ctx->regs); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to get all regulators\n"); + + ctx->num_vregs = ret; + + ctx->w_disable1_gpio = devm_gpiod_get_optional(dev, "w-disable1", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->w_disable1_gpio)) { + ret = dev_err_probe(dev, PTR_ERR(ctx->w_disable1_gpio), + "Failed to get the W_DISABLE_1# GPIO\n"); + goto err_free_regulators; + } + + ctx->w_disable2_gpio = devm_gpiod_get_optional(dev, "w-disable2", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->w_disable2_gpio)) { + ret = dev_err_probe(dev, PTR_ERR(ctx->w_disable2_gpio), + "Failed to get the W_DISABLE_2# GPIO\n"); + goto err_free_regulators; + } + + config.parent = dev; + config.owner = THIS_MODULE; + config.drvdata = ctx; + config.match = pwrseq_pcie_m2_match; + config.targets = ctx->pdata->targets; + + ctx->pwrseq = devm_pwrseq_device_register(dev, &config); + if (IS_ERR(ctx->pwrseq)) { + ret = dev_err_probe(dev, PTR_ERR(ctx->pwrseq), + "Failed to register the power sequencer\n"); + goto err_free_regulators; + } + + mutex_init(&ctx->list_lock); + INIT_LIST_HEAD(&ctx->pci_devices); + ctx->dev = dev; + + /* Create serdev for available PCI devices (if required) */ + ret = pwrseq_pcie_m2_create_serdev(ctx); + if (ret) + goto err_destroy_mutex; + + /* + * Register a notifier for creating protocol devices for + * non-discoverable busses like UART. + */ + ret = pwrseq_pcie_m2_register_notifier(ctx); + if (ret) + goto err_remove_serdev; + + return 0; + +err_remove_serdev: + pwrseq_pcie_m2_remove_serdev(ctx, NULL); +err_destroy_mutex: + mutex_destroy(&ctx->list_lock); +err_free_regulators: + regulator_bulk_free(ctx->num_vregs, ctx->regs); + + return ret; +} + +static void pwrseq_pcie_m2_remove(struct platform_device *pdev) +{ + struct pwrseq_pcie_m2_ctx *ctx = platform_get_drvdata(pdev); + + bus_unregister_notifier(&pci_bus_type, &ctx->nb); + pwrseq_pcie_m2_remove_serdev(ctx, NULL); + mutex_destroy(&ctx->list_lock); + + regulator_bulk_free(ctx->num_vregs, ctx->regs); +} + +static const struct of_device_id pwrseq_pcie_m2_of_match[] = { + { + .compatible = "pcie-m2-m-connector", + .data = &pwrseq_pcie_m2_m_of_data, + }, + { + .compatible = "pcie-m2-e-connector", + .data = &pwrseq_pcie_m2_e_of_data, + }, + { } +}; +MODULE_DEVICE_TABLE(of, pwrseq_pcie_m2_of_match); + +static struct platform_driver pwrseq_pcie_m2_driver = { + .driver = { + .name = "pwrseq-pcie-m2", + .of_match_table = pwrseq_pcie_m2_of_match, + }, + .probe = pwrseq_pcie_m2_probe, + .remove = pwrseq_pcie_m2_remove, +}; +module_platform_driver(pwrseq_pcie_m2_driver); + +MODULE_AUTHOR("Manivannan Sadhasivam "); +MODULE_DESCRIPTION("Power Sequencing driver for PCIe M.2 connector"); +MODULE_LICENSE("GPL"); diff --git a/drivers/tty/serdev/core.c b/drivers/tty/serdev/core.c index b33e708cb2455..6ee69c122550b 100644 --- a/drivers/tty/serdev/core.c +++ b/drivers/tty/serdev/core.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -504,6 +505,25 @@ struct serdev_controller *serdev_controller_alloc(struct device *host, } EXPORT_SYMBOL_GPL(serdev_controller_alloc); +#ifdef CONFIG_OF +/** + * of_find_serdev_controller_by_node() - Find the serdev controller associated + * with the devicetree node + * @node: Devicetree node + * + * Return: Pointer to the serdev controller associated with the node. NULL if + * the controller is not found. Caller is responsible for calling + * serdev_controller_put() to drop the reference. + */ +struct serdev_controller *of_find_serdev_controller_by_node(struct device_node *node) +{ + struct device *dev = bus_find_device_by_of_node(&serdev_bus_type, node); + + return (dev && dev->type == &serdev_ctrl_type) ? to_serdev_controller(dev) : NULL; +} +EXPORT_SYMBOL_GPL(of_find_serdev_controller_by_node); +#endif + static int of_serdev_register_devices(struct serdev_controller *ctrl) { struct device_node *node; @@ -532,7 +552,13 @@ static int of_serdev_register_devices(struct serdev_controller *ctrl) } else found = true; } - if (!found) + + /* + * When the serdev controller is connected to an external connector like + * M.2 in DT, then the serdev devices may be created dynamically by the + * connector driver. + */ + if (!found && !of_graph_is_present(dev_of_node(&ctrl->dev))) return -ENODEV; return 0; diff --git a/include/linux/pwrseq/consumer.h b/include/linux/pwrseq/consumer.h index 7d583b4f266e6..3c907c9e1885d 100644 --- a/include/linux/pwrseq/consumer.h +++ b/include/linux/pwrseq/consumer.h @@ -23,6 +23,8 @@ devm_pwrseq_get(struct device *dev, const char *target); int pwrseq_power_on(struct pwrseq_desc *desc); int pwrseq_power_off(struct pwrseq_desc *desc); +struct device *pwrseq_to_device(struct pwrseq_desc *desc); + #else /* CONFIG_POWER_SEQUENCING */ static inline struct pwrseq_desc * __must_check @@ -51,6 +53,11 @@ static inline int pwrseq_power_off(struct pwrseq_desc *desc) return -ENOSYS; } +static inline struct device *pwrseq_to_device(struct pwrseq_desc *desc) +{ + return NULL; +} + #endif /* CONFIG_POWER_SEQUENCING */ #endif /* __POWER_SEQUENCING_CONSUMER_H__ */ diff --git a/include/linux/serdev.h b/include/linux/serdev.h index 34562eb99931d..0728e6bb7abd4 100644 --- a/include/linux/serdev.h +++ b/include/linux/serdev.h @@ -49,10 +49,7 @@ struct serdev_device { struct mutex write_lock; }; -static inline struct serdev_device *to_serdev_device(struct device *d) -{ - return container_of(d, struct serdev_device, dev); -} +#define to_serdev_device(d) container_of_const(d, struct serdev_device, dev) /** * struct serdev_device_driver - serdev slave device driver @@ -67,10 +64,7 @@ struct serdev_device_driver { void (*remove)(struct serdev_device *); }; -static inline struct serdev_device_driver *to_serdev_device_driver(struct device_driver *d) -{ - return container_of(d, struct serdev_device_driver, driver); -} +#define to_serdev_device_driver(d) container_of_const(d, struct serdev_device_driver, driver) enum serdev_parity { SERDEV_PARITY_NONE, @@ -111,10 +105,7 @@ struct serdev_controller { const struct serdev_controller_ops *ops; }; -static inline struct serdev_controller *to_serdev_controller(struct device *d) -{ - return container_of(d, struct serdev_controller, dev); -} +#define to_serdev_controller(d) container_of_const(d, struct serdev_controller, dev) static inline void *serdev_device_get_drvdata(const struct serdev_device *serdev) { @@ -342,4 +333,13 @@ static inline bool serdev_acpi_get_uart_resource(struct acpi_resource *ares, } #endif /* CONFIG_ACPI */ +#ifdef CONFIG_OF +struct serdev_controller *of_find_serdev_controller_by_node(struct device_node *node); +#else +static inline struct serdev_controller *of_find_serdev_controller_by_node(struct device_node *node) +{ + return NULL; +} +#endif /* CONFIG_OF */ + #endif /*_LINUX_SERDEV_H */