| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. | |
| */ | |
| #include <linux/cleanup.h> | |
| #include <linux/delay.h> | |
| #include <linux/err.h> | |
| #include <linux/module.h> | |
| #include <linux/of.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/of_platform.h> | |
| #include <linux/regmap.h> | |
| #include <linux/spmi.h> | |
| #include <linux/soc/qcom/qcom-pbs.h> | |
| #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; | |
| guard(mutex)(&pbs->lock); | |
| ret = regmap_read(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH2, &val); | |
| if (ret < 0) | |
| return ret; | |
| 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) | |
| return ret; | |
| } | |
| 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) | |
| break; | |
| /* 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) | |
| break; | |
| /* 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) | |
| break; | |
| ret = qcom_pbs_wait_for_ack(pbs, bit_pos); | |
| if (ret < 0) | |
| break; | |
| /* 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); | |
| } | |
| /* Clear all the requested bitmap */ | |
| return regmap_update_bits(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH1, bitmap, 0); | |
| } | |
| 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 platform_device *pdev; | |
| struct pbs_dev *pbs; | |
| struct device_node *pbs_dev_node __free(device_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"); | |
| return ERR_PTR(-EPROBE_DEFER); | |
| } | |
| 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); | |
| return ERR_PTR(-EPROBE_DEFER); | |
| } | |
| 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); | |
| return ERR_PTR(-EINVAL); | |
| } | |
| platform_device_put(pdev); | |
| 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"); |