// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #define PBS_CLIENT_TRIG_CTL 0x42 #define PBS_CLIENT_SW_TRIG_BIT BIT(7) #define PBS_CLIENT_SCRATCH1 0x50 #define PBS_CLIENT_SCRATCH2 0x51 #define PBS_CLIENT_SCRATCH2_ERROR 0xFF #define RETRIES 2000 #define DELAY 1100 struct pbs_dev { struct device *dev; struct regmap *regmap; struct mutex lock; struct device_link *link; u32 base; }; static int qcom_pbs_wait_for_ack(struct pbs_dev *pbs, u8 bit_pos) { unsigned int val; int ret; ret = regmap_read_poll_timeout(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH2, val, val & BIT(bit_pos), DELAY, DELAY * RETRIES); if (ret < 0) { dev_err(pbs->dev, "Timeout for PBS ACK/NACK for bit %u\n", bit_pos); return -ETIMEDOUT; } if (val == PBS_CLIENT_SCRATCH2_ERROR) { ret = regmap_write(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH2, 0); dev_err(pbs->dev, "NACK from PBS for bit %u\n", bit_pos); return -EINVAL; } dev_dbg(pbs->dev, "PBS sequence for bit %u executed!\n", bit_pos); return 0; } /** * qcom_pbs_trigger_event() - Trigger the PBS RAM sequence * @pbs: Pointer to PBS device * @bitmap: bitmap * * This function is used to trigger the PBS RAM sequence to be * executed by the client driver. * * The PBS trigger sequence involves * 1. setting the PBS sequence bit in PBS_CLIENT_SCRATCH1 * 2. Initiating the SW PBS trigger * 3. Checking the equivalent bit in PBS_CLIENT_SCRATCH2 for the * completion of the sequence. * 4. If PBS_CLIENT_SCRATCH2 == 0xFF, the PBS sequence failed to execute * * Return: 0 on success, < 0 on failure */ int qcom_pbs_trigger_event(struct pbs_dev *pbs, u8 bitmap) { unsigned int val; u16 bit_pos; int ret; if (WARN_ON(!bitmap)) return -EINVAL; if (IS_ERR_OR_NULL(pbs)) return -EINVAL; mutex_lock(&pbs->lock); ret = regmap_read(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH2, &val); if (ret < 0) goto out; if (val == PBS_CLIENT_SCRATCH2_ERROR) { /* PBS error - clear SCRATCH2 register */ ret = regmap_write(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH2, 0); if (ret < 0) goto out; } for (bit_pos = 0; bit_pos < 8; bit_pos++) { if (!(bitmap & BIT(bit_pos))) continue; /* Clear the PBS sequence bit position */ ret = regmap_update_bits(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH2, BIT(bit_pos), 0); if (ret < 0) goto out_clear_scratch1; /* Set the PBS sequence bit position */ ret = regmap_update_bits(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH1, BIT(bit_pos), BIT(bit_pos)); if (ret < 0) goto out_clear_scratch1; /* Initiate the SW trigger */ ret = regmap_update_bits(pbs->regmap, pbs->base + PBS_CLIENT_TRIG_CTL, PBS_CLIENT_SW_TRIG_BIT, PBS_CLIENT_SW_TRIG_BIT); if (ret < 0) goto out_clear_scratch1; ret = qcom_pbs_wait_for_ack(pbs, bit_pos); if (ret < 0) goto out_clear_scratch1; /* Clear the PBS sequence bit position */ regmap_update_bits(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH1, BIT(bit_pos), 0); regmap_update_bits(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH2, BIT(bit_pos), 0); } out_clear_scratch1: /* Clear all the requested bitmap */ ret = regmap_update_bits(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH1, bitmap, 0); out: mutex_unlock(&pbs->lock); return ret; } EXPORT_SYMBOL_GPL(qcom_pbs_trigger_event); /** * get_pbs_client_device() - Get the PBS device used by client * @dev: Client device * * This function is used to get the PBS device that is being * used by the client. * * Return: pbs_dev on success, ERR_PTR on failure */ struct pbs_dev *get_pbs_client_device(struct device *dev) { struct device_node *pbs_dev_node; struct platform_device *pdev; struct pbs_dev *pbs; pbs_dev_node = of_parse_phandle(dev->of_node, "qcom,pbs", 0); if (!pbs_dev_node) { dev_err(dev, "Missing qcom,pbs property\n"); return ERR_PTR(-ENODEV); } pdev = of_find_device_by_node(pbs_dev_node); if (!pdev) { dev_err(dev, "Unable to find PBS dev_node\n"); pbs = ERR_PTR(-EPROBE_DEFER); goto out; } pbs = platform_get_drvdata(pdev); if (!pbs) { dev_err(dev, "Cannot get pbs instance from %s\n", dev_name(&pdev->dev)); platform_device_put(pdev); pbs = ERR_PTR(-EPROBE_DEFER); goto out; } pbs->link = device_link_add(dev, &pdev->dev, DL_FLAG_AUTOREMOVE_SUPPLIER); if (!pbs->link) { dev_err(&pdev->dev, "Failed to create device link to consumer %s\n", dev_name(dev)); platform_device_put(pdev); pbs = ERR_PTR(-EINVAL); goto out; } out: of_node_put(pbs_dev_node); return pbs; } EXPORT_SYMBOL_GPL(get_pbs_client_device); static int qcom_pbs_probe(struct platform_device *pdev) { struct pbs_dev *pbs; u32 val; int ret; pbs = devm_kzalloc(&pdev->dev, sizeof(*pbs), GFP_KERNEL); if (!pbs) return -ENOMEM; pbs->dev = &pdev->dev; pbs->regmap = dev_get_regmap(pbs->dev->parent, NULL); if (!pbs->regmap) { dev_err(pbs->dev, "Couldn't get parent's regmap\n"); return -EINVAL; } ret = device_property_read_u32(pbs->dev, "reg", &val); if (ret < 0) { dev_err(pbs->dev, "Couldn't find reg, ret = %d\n", ret); return ret; } pbs->base = val; mutex_init(&pbs->lock); platform_set_drvdata(pdev, pbs); return 0; } static const struct of_device_id qcom_pbs_match_table[] = { { .compatible = "qcom,pbs" }, {} }; MODULE_DEVICE_TABLE(of, qcom_pbs_match_table); static struct platform_driver qcom_pbs_driver = { .driver = { .name = "qcom-pbs", .of_match_table = qcom_pbs_match_table, }, .probe = qcom_pbs_probe, }; module_platform_driver(qcom_pbs_driver) MODULE_DESCRIPTION("QCOM PBS DRIVER"); MODULE_LICENSE("GPL");