Skip to content

Commit 3f736ae

Browse files
Mani-SadhasivamBartosz Golaszewski
authored andcommitted
power: sequencing: pcie-m2: Create serdev device for WCN7850 bluetooth
For supporting bluetooth over the non-discoverable UART interface of WCN7850, create the serdev device after enumerating the PCIe interface. This is mandatory since the device ID is only known after the PCIe enumeration and the ID is used for creating the serdev device. Since by default there is no OF or ACPI node for the created serdev, create a dynamic OF 'bluetooth' node with the 'compatible' property and attach it to the serdev device. This will allow the serdev device to bind to the existing bluetooth driver. Tested-by: Hans de Goede <johannes.goede@oss.qualcomm.com> # ThinkPad T14s gen6 (arm64) Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com> Link: https://patch.msgid.link/20260326-pci-m2-e-v7-8-43324a7866e6@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
1 parent 0d38285 commit 3f736ae

2 files changed

Lines changed: 240 additions & 16 deletions

File tree

drivers/power/sequencing/Kconfig

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ config POWER_SEQUENCING_TH1520_GPU
3737

3838
config POWER_SEQUENCING_PCIE_M2
3939
tristate "PCIe M.2 connector power sequencing driver"
40-
depends on OF || COMPILE_TEST
40+
depends on (PCI && OF) || COMPILE_TEST
41+
select OF_DYNAMIC if OF
4142
help
4243
Say Y here to enable the power sequencing driver for PCIe M.2
4344
connectors. This driver handles the power sequencing for the M.2

drivers/power/sequencing/pwrseq-pcie-m2.c

Lines changed: 238 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
#include <linux/of.h>
1313
#include <linux/of_graph.h>
1414
#include <linux/of_platform.h>
15+
#include <linux/pci.h>
1516
#include <linux/platform_device.h>
1617
#include <linux/pwrseq/provider.h>
1718
#include <linux/regulator/consumer.h>
19+
#include <linux/serdev.h>
1820
#include <linux/slab.h>
1921

2022
struct pwrseq_pcie_m2_pdata {
@@ -30,6 +32,9 @@ struct pwrseq_pcie_m2_ctx {
3032
struct notifier_block nb;
3133
struct gpio_desc *w_disable1_gpio;
3234
struct gpio_desc *w_disable2_gpio;
35+
struct serdev_device *serdev;
36+
struct of_changeset *ocs;
37+
struct device *dev;
3338
};
3439

3540
static int pwrseq_pcie_m2_vregs_enable(struct pwrseq_device *pwrseq)
@@ -172,11 +177,202 @@ static int pwrseq_pcie_m2_match(struct pwrseq_device *pwrseq,
172177
return PWRSEQ_NO_MATCH;
173178
}
174179

175-
static void pwrseq_pcie_m2_free_regulators(void *data)
180+
static int pwrseq_m2_pcie_create_bt_node(struct pwrseq_pcie_m2_ctx *ctx,
181+
struct device_node *parent)
176182
{
177-
struct pwrseq_pcie_m2_ctx *ctx = data;
183+
struct device *dev = ctx->dev;
184+
struct device_node *np;
185+
int ret;
178186

179-
regulator_bulk_free(ctx->num_vregs, ctx->regs);
187+
ctx->ocs = kzalloc_obj(*ctx->ocs);
188+
if (!ctx->ocs)
189+
return -ENOMEM;
190+
191+
of_changeset_init(ctx->ocs);
192+
193+
np = of_changeset_create_node(ctx->ocs, parent, "bluetooth");
194+
if (!np) {
195+
dev_err(dev, "Failed to create bluetooth node\n");
196+
ret = -ENODEV;
197+
goto err_destroy_changeset;
198+
}
199+
200+
ret = of_changeset_add_prop_string(ctx->ocs, np, "compatible", "qcom,wcn7850-bt");
201+
if (ret) {
202+
dev_err(dev, "Failed to add bluetooth compatible: %d\n", ret);
203+
goto err_destroy_changeset;
204+
}
205+
206+
ret = of_changeset_apply(ctx->ocs);
207+
if (ret) {
208+
dev_err(dev, "Failed to apply changeset: %d\n", ret);
209+
goto err_destroy_changeset;
210+
}
211+
212+
ret = device_add_of_node(&ctx->serdev->dev, np);
213+
if (ret) {
214+
dev_err(dev, "Failed to add OF node: %d\n", ret);
215+
goto err_revert_changeset;
216+
}
217+
218+
return 0;
219+
220+
err_revert_changeset:
221+
of_changeset_revert(ctx->ocs);
222+
err_destroy_changeset:
223+
of_changeset_destroy(ctx->ocs);
224+
kfree(ctx->ocs);
225+
ctx->ocs = NULL;
226+
227+
return ret;
228+
}
229+
230+
static int pwrseq_pcie_m2_create_serdev(struct pwrseq_pcie_m2_ctx *ctx)
231+
{
232+
struct serdev_controller *serdev_ctrl;
233+
struct device *dev = ctx->dev;
234+
int ret;
235+
236+
struct device_node *serdev_parent __free(device_node) =
237+
of_graph_get_remote_node(dev_of_node(ctx->dev), 3, 0);
238+
if (!serdev_parent)
239+
return 0;
240+
241+
serdev_ctrl = of_find_serdev_controller_by_node(serdev_parent);
242+
if (!serdev_ctrl)
243+
return 0;
244+
245+
/* Bail out if the device was already attached to this controller */
246+
if (serdev_ctrl->serdev) {
247+
serdev_controller_put(serdev_ctrl);
248+
return 0;
249+
}
250+
251+
ctx->serdev = serdev_device_alloc(serdev_ctrl);
252+
if (!ctx->serdev) {
253+
ret = -ENOMEM;
254+
goto err_put_ctrl;
255+
}
256+
257+
ret = pwrseq_m2_pcie_create_bt_node(ctx, serdev_parent);
258+
if (ret)
259+
goto err_free_serdev;
260+
261+
ret = serdev_device_add(ctx->serdev);
262+
if (ret) {
263+
dev_err(dev, "Failed to add serdev for WCN7850: %d\n", ret);
264+
goto err_free_dt_node;
265+
}
266+
267+
serdev_controller_put(serdev_ctrl);
268+
269+
return 0;
270+
271+
err_free_dt_node:
272+
device_remove_of_node(&ctx->serdev->dev);
273+
of_changeset_revert(ctx->ocs);
274+
of_changeset_destroy(ctx->ocs);
275+
kfree(ctx->ocs);
276+
ctx->ocs = NULL;
277+
err_free_serdev:
278+
serdev_device_put(ctx->serdev);
279+
ctx->serdev = NULL;
280+
err_put_ctrl:
281+
serdev_controller_put(serdev_ctrl);
282+
283+
return ret;
284+
}
285+
286+
static void pwrseq_pcie_m2_remove_serdev(struct pwrseq_pcie_m2_ctx *ctx)
287+
{
288+
if (ctx->serdev) {
289+
device_remove_of_node(&ctx->serdev->dev);
290+
serdev_device_remove(ctx->serdev);
291+
ctx->serdev = NULL;
292+
}
293+
294+
if (ctx->ocs) {
295+
of_changeset_revert(ctx->ocs);
296+
of_changeset_destroy(ctx->ocs);
297+
kfree(ctx->ocs);
298+
ctx->ocs = NULL;
299+
}
300+
}
301+
302+
static int pwrseq_m2_pcie_notify(struct notifier_block *nb, unsigned long action,
303+
void *data)
304+
{
305+
struct pwrseq_pcie_m2_ctx *ctx = container_of(nb, struct pwrseq_pcie_m2_ctx, nb);
306+
struct pci_dev *pdev = to_pci_dev(data);
307+
int ret;
308+
309+
/*
310+
* Check whether the PCI device is associated with this M.2 connector or
311+
* not, by comparing the OF node of the PCI device parent and the Port 0
312+
* (PCIe) remote node parent OF node.
313+
*/
314+
struct device_node *pci_parent __free(device_node) =
315+
of_graph_get_remote_node(dev_of_node(ctx->dev), 0, 0);
316+
if (!pci_parent || (pci_parent != pdev->dev.parent->of_node))
317+
return NOTIFY_DONE;
318+
319+
switch (action) {
320+
case BUS_NOTIFY_ADD_DEVICE:
321+
/* Create serdev device for WCN7850 */
322+
if (pdev->vendor == PCI_VENDOR_ID_QCOM && pdev->device == 0x1107) {
323+
ret = pwrseq_pcie_m2_create_serdev(ctx);
324+
if (ret)
325+
return notifier_from_errno(ret);
326+
}
327+
break;
328+
case BUS_NOTIFY_REMOVED_DEVICE:
329+
/* Destroy serdev device for WCN7850 */
330+
if (pdev->vendor == PCI_VENDOR_ID_QCOM && pdev->device == 0x1107)
331+
pwrseq_pcie_m2_remove_serdev(ctx);
332+
333+
break;
334+
}
335+
336+
return NOTIFY_OK;
337+
}
338+
339+
static bool pwrseq_pcie_m2_check_remote_node(struct device *dev, u8 port, u8 endpoint,
340+
const char *node)
341+
{
342+
struct device_node *remote __free(device_node) =
343+
of_graph_get_remote_node(dev_of_node(dev), port, endpoint);
344+
345+
if (remote && of_node_name_eq(remote, node))
346+
return true;
347+
348+
return false;
349+
}
350+
351+
/*
352+
* If the connector exposes a non-discoverable bus like UART, the respective
353+
* protocol device needs to be created manually with the help of the notifier
354+
* of the discoverable bus like PCIe.
355+
*/
356+
static int pwrseq_pcie_m2_register_notifier(struct pwrseq_pcie_m2_ctx *ctx, struct device *dev)
357+
{
358+
int ret;
359+
360+
/*
361+
* Register a PCI notifier for Key E connector that has PCIe as Port
362+
* 0/Endpoint 0 interface and Serial as Port 3/Endpoint 0 interface.
363+
*/
364+
if (pwrseq_pcie_m2_check_remote_node(dev, 3, 0, "serial")) {
365+
if (pwrseq_pcie_m2_check_remote_node(dev, 0, 0, "pcie")) {
366+
ctx->dev = dev;
367+
ctx->nb.notifier_call = pwrseq_m2_pcie_notify;
368+
ret = bus_register_notifier(&pci_bus_type, &ctx->nb);
369+
if (ret)
370+
return dev_err_probe(dev, ret,
371+
"Failed to register notifier for serdev\n");
372+
}
373+
}
374+
375+
return 0;
180376
}
181377

182378
static int pwrseq_pcie_m2_probe(struct platform_device *pdev)
@@ -190,6 +386,7 @@ static int pwrseq_pcie_m2_probe(struct platform_device *pdev)
190386
if (!ctx)
191387
return -ENOMEM;
192388

389+
platform_set_drvdata(pdev, ctx);
193390
ctx->of_node = of_node_get(dev->of_node);
194391
ctx->pdata = device_get_match_data(dev);
195392
if (!ctx->pdata)
@@ -206,21 +403,21 @@ static int pwrseq_pcie_m2_probe(struct platform_device *pdev)
206403
return dev_err_probe(dev, ret,
207404
"Failed to get all regulators\n");
208405

406+
ctx->num_vregs = ret;
407+
209408
ctx->w_disable1_gpio = devm_gpiod_get_optional(dev, "w-disable1", GPIOD_OUT_HIGH);
210-
if (IS_ERR(ctx->w_disable1_gpio))
211-
return dev_err_probe(dev, PTR_ERR(ctx->w_disable1_gpio),
409+
if (IS_ERR(ctx->w_disable1_gpio)) {
410+
ret = dev_err_probe(dev, PTR_ERR(ctx->w_disable1_gpio),
212411
"Failed to get the W_DISABLE_1# GPIO\n");
412+
goto err_free_regulators;
413+
}
213414

214415
ctx->w_disable2_gpio = devm_gpiod_get_optional(dev, "w-disable2", GPIOD_OUT_HIGH);
215-
if (IS_ERR(ctx->w_disable2_gpio))
216-
return dev_err_probe(dev, PTR_ERR(ctx->w_disable2_gpio),
416+
if (IS_ERR(ctx->w_disable2_gpio)) {
417+
ret = dev_err_probe(dev, PTR_ERR(ctx->w_disable2_gpio),
217418
"Failed to get the W_DISABLE_2# GPIO\n");
218-
219-
ctx->num_vregs = ret;
220-
221-
ret = devm_add_action_or_reset(dev, pwrseq_pcie_m2_free_regulators, ctx);
222-
if (ret)
223-
return ret;
419+
goto err_free_regulators;
420+
}
224421

225422
config.parent = dev;
226423
config.owner = THIS_MODULE;
@@ -229,11 +426,36 @@ static int pwrseq_pcie_m2_probe(struct platform_device *pdev)
229426
config.targets = ctx->pdata->targets;
230427

231428
ctx->pwrseq = devm_pwrseq_device_register(dev, &config);
232-
if (IS_ERR(ctx->pwrseq))
233-
return dev_err_probe(dev, PTR_ERR(ctx->pwrseq),
429+
if (IS_ERR(ctx->pwrseq)) {
430+
ret = dev_err_probe(dev, PTR_ERR(ctx->pwrseq),
234431
"Failed to register the power sequencer\n");
432+
goto err_free_regulators;
433+
}
434+
435+
/*
436+
* Register a notifier for creating protocol devices for
437+
* non-discoverable busses like UART.
438+
*/
439+
ret = pwrseq_pcie_m2_register_notifier(ctx, dev);
440+
if (ret)
441+
goto err_free_regulators;
235442

236443
return 0;
444+
445+
err_free_regulators:
446+
regulator_bulk_free(ctx->num_vregs, ctx->regs);
447+
448+
return ret;
449+
}
450+
451+
static void pwrseq_pcie_m2_remove(struct platform_device *pdev)
452+
{
453+
struct pwrseq_pcie_m2_ctx *ctx = platform_get_drvdata(pdev);
454+
455+
bus_unregister_notifier(&pci_bus_type, &ctx->nb);
456+
pwrseq_pcie_m2_remove_serdev(ctx);
457+
458+
regulator_bulk_free(ctx->num_vregs, ctx->regs);
237459
}
238460

239461
static const struct of_device_id pwrseq_pcie_m2_of_match[] = {
@@ -255,6 +477,7 @@ static struct platform_driver pwrseq_pcie_m2_driver = {
255477
.of_match_table = pwrseq_pcie_m2_of_match,
256478
},
257479
.probe = pwrseq_pcie_m2_probe,
480+
.remove = pwrseq_pcie_m2_remove,
258481
};
259482
module_platform_driver(pwrseq_pcie_m2_driver);
260483

0 commit comments

Comments
 (0)