1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. |
4 | */ |
5 | |
6 | #include <linux/delay.h> |
7 | #include <linux/err.h> |
8 | #include <linux/module.h> |
9 | #include <linux/of.h> |
10 | #include <linux/platform_device.h> |
11 | #include <linux/of_platform.h> |
12 | #include <linux/regmap.h> |
13 | #include <linux/spmi.h> |
14 | #include <linux/soc/qcom/qcom-pbs.h> |
15 | |
16 | #define PBS_CLIENT_TRIG_CTL 0x42 |
17 | #define PBS_CLIENT_SW_TRIG_BIT BIT(7) |
18 | #define PBS_CLIENT_SCRATCH1 0x50 |
19 | #define PBS_CLIENT_SCRATCH2 0x51 |
20 | #define PBS_CLIENT_SCRATCH2_ERROR 0xFF |
21 | |
22 | #define RETRIES 2000 |
23 | #define DELAY 1100 |
24 | |
25 | struct pbs_dev { |
26 | struct device *dev; |
27 | struct regmap *regmap; |
28 | struct mutex lock; |
29 | struct device_link *link; |
30 | |
31 | u32 base; |
32 | }; |
33 | |
34 | static int qcom_pbs_wait_for_ack(struct pbs_dev *pbs, u8 bit_pos) |
35 | { |
36 | unsigned int val; |
37 | int ret; |
38 | |
39 | ret = regmap_read_poll_timeout(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH2, |
40 | val, val & BIT(bit_pos), DELAY, DELAY * RETRIES); |
41 | |
42 | if (ret < 0) { |
43 | dev_err(pbs->dev, "Timeout for PBS ACK/NACK for bit %u\n" , bit_pos); |
44 | return -ETIMEDOUT; |
45 | } |
46 | |
47 | if (val == PBS_CLIENT_SCRATCH2_ERROR) { |
48 | ret = regmap_write(map: pbs->regmap, reg: pbs->base + PBS_CLIENT_SCRATCH2, val: 0); |
49 | dev_err(pbs->dev, "NACK from PBS for bit %u\n" , bit_pos); |
50 | return -EINVAL; |
51 | } |
52 | |
53 | dev_dbg(pbs->dev, "PBS sequence for bit %u executed!\n" , bit_pos); |
54 | return 0; |
55 | } |
56 | |
57 | /** |
58 | * qcom_pbs_trigger_event() - Trigger the PBS RAM sequence |
59 | * @pbs: Pointer to PBS device |
60 | * @bitmap: bitmap |
61 | * |
62 | * This function is used to trigger the PBS RAM sequence to be |
63 | * executed by the client driver. |
64 | * |
65 | * The PBS trigger sequence involves |
66 | * 1. setting the PBS sequence bit in PBS_CLIENT_SCRATCH1 |
67 | * 2. Initiating the SW PBS trigger |
68 | * 3. Checking the equivalent bit in PBS_CLIENT_SCRATCH2 for the |
69 | * completion of the sequence. |
70 | * 4. If PBS_CLIENT_SCRATCH2 == 0xFF, the PBS sequence failed to execute |
71 | * |
72 | * Return: 0 on success, < 0 on failure |
73 | */ |
74 | int qcom_pbs_trigger_event(struct pbs_dev *pbs, u8 bitmap) |
75 | { |
76 | unsigned int val; |
77 | u16 bit_pos; |
78 | int ret; |
79 | |
80 | if (WARN_ON(!bitmap)) |
81 | return -EINVAL; |
82 | |
83 | if (IS_ERR_OR_NULL(ptr: pbs)) |
84 | return -EINVAL; |
85 | |
86 | mutex_lock(&pbs->lock); |
87 | ret = regmap_read(map: pbs->regmap, reg: pbs->base + PBS_CLIENT_SCRATCH2, val: &val); |
88 | if (ret < 0) |
89 | goto out; |
90 | |
91 | if (val == PBS_CLIENT_SCRATCH2_ERROR) { |
92 | /* PBS error - clear SCRATCH2 register */ |
93 | ret = regmap_write(map: pbs->regmap, reg: pbs->base + PBS_CLIENT_SCRATCH2, val: 0); |
94 | if (ret < 0) |
95 | goto out; |
96 | } |
97 | |
98 | for (bit_pos = 0; bit_pos < 8; bit_pos++) { |
99 | if (!(bitmap & BIT(bit_pos))) |
100 | continue; |
101 | |
102 | /* Clear the PBS sequence bit position */ |
103 | ret = regmap_update_bits(map: pbs->regmap, reg: pbs->base + PBS_CLIENT_SCRATCH2, |
104 | BIT(bit_pos), val: 0); |
105 | if (ret < 0) |
106 | goto out_clear_scratch1; |
107 | |
108 | /* Set the PBS sequence bit position */ |
109 | ret = regmap_update_bits(map: pbs->regmap, reg: pbs->base + PBS_CLIENT_SCRATCH1, |
110 | BIT(bit_pos), BIT(bit_pos)); |
111 | if (ret < 0) |
112 | goto out_clear_scratch1; |
113 | |
114 | /* Initiate the SW trigger */ |
115 | ret = regmap_update_bits(map: pbs->regmap, reg: pbs->base + PBS_CLIENT_TRIG_CTL, |
116 | PBS_CLIENT_SW_TRIG_BIT, PBS_CLIENT_SW_TRIG_BIT); |
117 | if (ret < 0) |
118 | goto out_clear_scratch1; |
119 | |
120 | ret = qcom_pbs_wait_for_ack(pbs, bit_pos); |
121 | if (ret < 0) |
122 | goto out_clear_scratch1; |
123 | |
124 | /* Clear the PBS sequence bit position */ |
125 | regmap_update_bits(map: pbs->regmap, reg: pbs->base + PBS_CLIENT_SCRATCH1, BIT(bit_pos), val: 0); |
126 | regmap_update_bits(map: pbs->regmap, reg: pbs->base + PBS_CLIENT_SCRATCH2, BIT(bit_pos), val: 0); |
127 | } |
128 | |
129 | out_clear_scratch1: |
130 | /* Clear all the requested bitmap */ |
131 | ret = regmap_update_bits(map: pbs->regmap, reg: pbs->base + PBS_CLIENT_SCRATCH1, mask: bitmap, val: 0); |
132 | |
133 | out: |
134 | mutex_unlock(lock: &pbs->lock); |
135 | |
136 | return ret; |
137 | } |
138 | EXPORT_SYMBOL_GPL(qcom_pbs_trigger_event); |
139 | |
140 | /** |
141 | * get_pbs_client_device() - Get the PBS device used by client |
142 | * @dev: Client device |
143 | * |
144 | * This function is used to get the PBS device that is being |
145 | * used by the client. |
146 | * |
147 | * Return: pbs_dev on success, ERR_PTR on failure |
148 | */ |
149 | struct pbs_dev *get_pbs_client_device(struct device *dev) |
150 | { |
151 | struct device_node *pbs_dev_node; |
152 | struct platform_device *pdev; |
153 | struct pbs_dev *pbs; |
154 | |
155 | pbs_dev_node = of_parse_phandle(np: dev->of_node, phandle_name: "qcom,pbs" , index: 0); |
156 | if (!pbs_dev_node) { |
157 | dev_err(dev, "Missing qcom,pbs property\n" ); |
158 | return ERR_PTR(error: -ENODEV); |
159 | } |
160 | |
161 | pdev = of_find_device_by_node(np: pbs_dev_node); |
162 | if (!pdev) { |
163 | dev_err(dev, "Unable to find PBS dev_node\n" ); |
164 | pbs = ERR_PTR(error: -EPROBE_DEFER); |
165 | goto out; |
166 | } |
167 | |
168 | pbs = platform_get_drvdata(pdev); |
169 | if (!pbs) { |
170 | dev_err(dev, "Cannot get pbs instance from %s\n" , dev_name(&pdev->dev)); |
171 | platform_device_put(pdev); |
172 | pbs = ERR_PTR(error: -EPROBE_DEFER); |
173 | goto out; |
174 | } |
175 | |
176 | pbs->link = device_link_add(consumer: dev, supplier: &pdev->dev, DL_FLAG_AUTOREMOVE_SUPPLIER); |
177 | if (!pbs->link) { |
178 | dev_err(&pdev->dev, "Failed to create device link to consumer %s\n" , dev_name(dev)); |
179 | platform_device_put(pdev); |
180 | pbs = ERR_PTR(error: -EINVAL); |
181 | goto out; |
182 | } |
183 | |
184 | out: |
185 | of_node_put(node: pbs_dev_node); |
186 | return pbs; |
187 | } |
188 | EXPORT_SYMBOL_GPL(get_pbs_client_device); |
189 | |
190 | static int qcom_pbs_probe(struct platform_device *pdev) |
191 | { |
192 | struct pbs_dev *pbs; |
193 | u32 val; |
194 | int ret; |
195 | |
196 | pbs = devm_kzalloc(dev: &pdev->dev, size: sizeof(*pbs), GFP_KERNEL); |
197 | if (!pbs) |
198 | return -ENOMEM; |
199 | |
200 | pbs->dev = &pdev->dev; |
201 | pbs->regmap = dev_get_regmap(dev: pbs->dev->parent, NULL); |
202 | if (!pbs->regmap) { |
203 | dev_err(pbs->dev, "Couldn't get parent's regmap\n" ); |
204 | return -EINVAL; |
205 | } |
206 | |
207 | ret = device_property_read_u32(dev: pbs->dev, propname: "reg" , val: &val); |
208 | if (ret < 0) { |
209 | dev_err(pbs->dev, "Couldn't find reg, ret = %d\n" , ret); |
210 | return ret; |
211 | } |
212 | pbs->base = val; |
213 | mutex_init(&pbs->lock); |
214 | |
215 | platform_set_drvdata(pdev, data: pbs); |
216 | |
217 | return 0; |
218 | } |
219 | |
220 | static const struct of_device_id qcom_pbs_match_table[] = { |
221 | { .compatible = "qcom,pbs" }, |
222 | {} |
223 | }; |
224 | MODULE_DEVICE_TABLE(of, qcom_pbs_match_table); |
225 | |
226 | static struct platform_driver qcom_pbs_driver = { |
227 | .driver = { |
228 | .name = "qcom-pbs" , |
229 | .of_match_table = qcom_pbs_match_table, |
230 | }, |
231 | .probe = qcom_pbs_probe, |
232 | }; |
233 | module_platform_driver(qcom_pbs_driver) |
234 | |
235 | MODULE_DESCRIPTION("QCOM PBS DRIVER" ); |
236 | MODULE_LICENSE("GPL" ); |
237 | |