Commit | Line | Data |
---|---|---|
9952f691 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
0297ffa6 SSM |
2 | /* |
3 | * drivers/i2c/busses/i2c-tegra-bpmp.c | |
4 | * | |
5 | * Copyright (c) 2016 NVIDIA Corporation. All rights reserved. | |
6 | * | |
7 | * Author: Shardar Shariff Md <smohammed@nvidia.com> | |
0297ffa6 SSM |
8 | */ |
9 | ||
10 | #include <linux/err.h> | |
11 | #include <linux/i2c.h> | |
12 | #include <linux/init.h> | |
13 | #include <linux/kernel.h> | |
14 | #include <linux/module.h> | |
15 | #include <linux/of_device.h> | |
16 | #include <linux/platform_device.h> | |
17 | #include <linux/pm_runtime.h> | |
18 | ||
19 | #include <soc/tegra/bpmp-abi.h> | |
20 | #include <soc/tegra/bpmp.h> | |
21 | ||
22 | /* | |
23 | * Serialized I2C message header size is 6 bytes and includes address, flags | |
24 | * and length | |
25 | */ | |
26 | #define SERIALI2C_HDR_SIZE 6 | |
27 | ||
28 | struct tegra_bpmp_i2c { | |
29 | struct i2c_adapter adapter; | |
30 | struct device *dev; | |
31 | ||
32 | struct tegra_bpmp *bpmp; | |
33 | unsigned int bus; | |
34 | }; | |
35 | ||
36 | /* | |
37 | * Linux flags are translated to BPMP defined I2C flags that are used in BPMP | |
38 | * firmware I2C driver to avoid any issues in future if Linux I2C flags are | |
39 | * changed. | |
40 | */ | |
41 | static int tegra_bpmp_xlate_flags(u16 flags, u16 *out) | |
42 | { | |
43 | if (flags & I2C_M_TEN) { | |
44 | *out |= SERIALI2C_TEN; | |
45 | flags &= ~I2C_M_TEN; | |
46 | } | |
47 | ||
48 | if (flags & I2C_M_RD) { | |
49 | *out |= SERIALI2C_RD; | |
50 | flags &= ~I2C_M_RD; | |
51 | } | |
52 | ||
53 | if (flags & I2C_M_STOP) { | |
54 | *out |= SERIALI2C_STOP; | |
55 | flags &= ~I2C_M_STOP; | |
56 | } | |
57 | ||
58 | if (flags & I2C_M_NOSTART) { | |
59 | *out |= SERIALI2C_NOSTART; | |
60 | flags &= ~I2C_M_NOSTART; | |
61 | } | |
62 | ||
63 | if (flags & I2C_M_REV_DIR_ADDR) { | |
64 | *out |= SERIALI2C_REV_DIR_ADDR; | |
65 | flags &= ~I2C_M_REV_DIR_ADDR; | |
66 | } | |
67 | ||
68 | if (flags & I2C_M_IGNORE_NAK) { | |
69 | *out |= SERIALI2C_IGNORE_NAK; | |
70 | flags &= ~I2C_M_IGNORE_NAK; | |
71 | } | |
72 | ||
73 | if (flags & I2C_M_NO_RD_ACK) { | |
74 | *out |= SERIALI2C_NO_RD_ACK; | |
75 | flags &= ~I2C_M_NO_RD_ACK; | |
76 | } | |
77 | ||
78 | if (flags & I2C_M_RECV_LEN) { | |
79 | *out |= SERIALI2C_RECV_LEN; | |
80 | flags &= ~I2C_M_RECV_LEN; | |
81 | } | |
82 | ||
bc1c2048 | 83 | return 0; |
0297ffa6 SSM |
84 | } |
85 | ||
86 | /** | |
87 | * The serialized I2C format is simply the following: | |
88 | * [addr little-endian][flags little-endian][len little-endian][data if write] | |
89 | * [addr little-endian][flags little-endian][len little-endian][data if write] | |
90 | * ... | |
91 | * | |
92 | * The flags are translated from Linux kernel representation to seriali2c | |
93 | * representation. Any undefined flag being set causes an error. | |
94 | * | |
95 | * The data is there only for writes. Reads have the data transferred in the | |
96 | * other direction, and thus data is not present. | |
97 | * | |
98 | * See deserialize_i2c documentation for the data format in the other direction. | |
99 | */ | |
100 | static int tegra_bpmp_serialize_i2c_msg(struct tegra_bpmp_i2c *i2c, | |
101 | struct mrq_i2c_request *request, | |
102 | struct i2c_msg *msgs, | |
103 | unsigned int num) | |
104 | { | |
105 | char *buf = request->xfer.data_buf; | |
106 | unsigned int i, j, pos = 0; | |
107 | int err; | |
108 | ||
109 | for (i = 0; i < num; i++) { | |
110 | struct i2c_msg *msg = &msgs[i]; | |
111 | u16 flags = 0; | |
112 | ||
113 | err = tegra_bpmp_xlate_flags(msg->flags, &flags); | |
114 | if (err < 0) | |
115 | return err; | |
116 | ||
117 | buf[pos++] = msg->addr & 0xff; | |
118 | buf[pos++] = (msg->addr & 0xff00) >> 8; | |
119 | buf[pos++] = flags & 0xff; | |
120 | buf[pos++] = (flags & 0xff00) >> 8; | |
121 | buf[pos++] = msg->len & 0xff; | |
122 | buf[pos++] = (msg->len & 0xff00) >> 8; | |
123 | ||
124 | if ((flags & SERIALI2C_RD) == 0) { | |
125 | for (j = 0; j < msg->len; j++) | |
126 | buf[pos++] = msg->buf[j]; | |
127 | } | |
128 | } | |
129 | ||
130 | request->xfer.data_size = pos; | |
131 | ||
132 | return 0; | |
133 | } | |
134 | ||
135 | /** | |
136 | * The data in the BPMP -> CPU direction is composed of sequential blocks for | |
137 | * those messages that have I2C_M_RD. So, for example, if you have: | |
138 | * | |
139 | * - !I2C_M_RD, len == 5, data == a0 01 02 03 04 | |
140 | * - !I2C_M_RD, len == 1, data == a0 | |
141 | * - I2C_M_RD, len == 2, data == [uninitialized buffer 1] | |
142 | * - !I2C_M_RD, len == 1, data == a2 | |
143 | * - I2C_M_RD, len == 2, data == [uninitialized buffer 2] | |
144 | * | |
145 | * ...then the data in the BPMP -> CPU direction would be 4 bytes total, and | |
146 | * would contain 2 bytes that will go to uninitialized buffer 1, and 2 bytes | |
147 | * that will go to uninitialized buffer 2. | |
148 | */ | |
149 | static int tegra_bpmp_i2c_deserialize(struct tegra_bpmp_i2c *i2c, | |
150 | struct mrq_i2c_response *response, | |
151 | struct i2c_msg *msgs, | |
152 | unsigned int num) | |
153 | { | |
154 | size_t size = response->xfer.data_size, len = 0, pos = 0; | |
155 | char *buf = response->xfer.data_buf; | |
156 | unsigned int i; | |
157 | ||
158 | for (i = 0; i < num; i++) | |
159 | if (msgs[i].flags & I2C_M_RD) | |
160 | len += msgs[i].len; | |
161 | ||
162 | if (len != size) | |
163 | return -EINVAL; | |
164 | ||
165 | for (i = 0; i < num; i++) { | |
166 | if (msgs[i].flags & I2C_M_RD) { | |
167 | memcpy(msgs[i].buf, buf + pos, msgs[i].len); | |
168 | pos += msgs[i].len; | |
169 | } | |
170 | } | |
171 | ||
172 | return 0; | |
173 | } | |
174 | ||
175 | static int tegra_bpmp_i2c_msg_len_check(struct i2c_msg *msgs, unsigned int num) | |
176 | { | |
177 | size_t tx_len = 0, rx_len = 0; | |
178 | unsigned int i; | |
179 | ||
180 | for (i = 0; i < num; i++) | |
181 | if (!(msgs[i].flags & I2C_M_RD)) | |
182 | tx_len += SERIALI2C_HDR_SIZE + msgs[i].len; | |
183 | ||
184 | if (tx_len > TEGRA_I2C_IPC_MAX_IN_BUF_SIZE) | |
185 | return -EINVAL; | |
186 | ||
187 | for (i = 0; i < num; i++) | |
188 | if ((msgs[i].flags & I2C_M_RD)) | |
189 | rx_len += msgs[i].len; | |
190 | ||
191 | if (rx_len > TEGRA_I2C_IPC_MAX_OUT_BUF_SIZE) | |
192 | return -EINVAL; | |
193 | ||
194 | return 0; | |
195 | } | |
196 | ||
197 | static int tegra_bpmp_i2c_msg_xfer(struct tegra_bpmp_i2c *i2c, | |
198 | struct mrq_i2c_request *request, | |
08960b02 WS |
199 | struct mrq_i2c_response *response, |
200 | bool atomic) | |
0297ffa6 SSM |
201 | { |
202 | struct tegra_bpmp_message msg; | |
203 | int err; | |
204 | ||
205 | request->cmd = CMD_I2C_XFER; | |
206 | request->xfer.bus_id = i2c->bus; | |
207 | ||
208 | memset(&msg, 0, sizeof(msg)); | |
209 | msg.mrq = MRQ_I2C; | |
210 | msg.tx.data = request; | |
211 | msg.tx.size = sizeof(*request); | |
212 | msg.rx.data = response; | |
213 | msg.rx.size = sizeof(*response); | |
214 | ||
08960b02 | 215 | if (atomic) |
0297ffa6 SSM |
216 | err = tegra_bpmp_transfer_atomic(i2c->bpmp, &msg); |
217 | else | |
218 | err = tegra_bpmp_transfer(i2c->bpmp, &msg); | |
219 | ||
220 | return err; | |
221 | } | |
222 | ||
08960b02 WS |
223 | static int tegra_bpmp_i2c_xfer_common(struct i2c_adapter *adapter, |
224 | struct i2c_msg *msgs, int num, | |
225 | bool atomic) | |
0297ffa6 SSM |
226 | { |
227 | struct tegra_bpmp_i2c *i2c = i2c_get_adapdata(adapter); | |
228 | struct mrq_i2c_response response; | |
229 | struct mrq_i2c_request request; | |
230 | int err; | |
231 | ||
232 | err = tegra_bpmp_i2c_msg_len_check(msgs, num); | |
233 | if (err < 0) { | |
234 | dev_err(i2c->dev, "unsupported message length\n"); | |
235 | return err; | |
236 | } | |
237 | ||
238 | memset(&request, 0, sizeof(request)); | |
239 | memset(&response, 0, sizeof(response)); | |
240 | ||
241 | err = tegra_bpmp_serialize_i2c_msg(i2c, &request, msgs, num); | |
242 | if (err < 0) { | |
243 | dev_err(i2c->dev, "failed to serialize message: %d\n", err); | |
244 | return err; | |
245 | } | |
246 | ||
08960b02 | 247 | err = tegra_bpmp_i2c_msg_xfer(i2c, &request, &response, atomic); |
0297ffa6 SSM |
248 | if (err < 0) { |
249 | dev_err(i2c->dev, "failed to transfer message: %d\n", err); | |
250 | return err; | |
251 | } | |
252 | ||
253 | err = tegra_bpmp_i2c_deserialize(i2c, &response, msgs, num); | |
254 | if (err < 0) { | |
255 | dev_err(i2c->dev, "failed to deserialize message: %d\n", err); | |
256 | return err; | |
257 | } | |
258 | ||
259 | return num; | |
260 | } | |
261 | ||
08960b02 WS |
262 | static int tegra_bpmp_i2c_xfer(struct i2c_adapter *adapter, |
263 | struct i2c_msg *msgs, int num) | |
264 | { | |
265 | return tegra_bpmp_i2c_xfer_common(adapter, msgs, num, false); | |
266 | } | |
267 | ||
268 | static int tegra_bpmp_i2c_xfer_atomic(struct i2c_adapter *adapter, | |
269 | struct i2c_msg *msgs, int num) | |
270 | { | |
271 | return tegra_bpmp_i2c_xfer_common(adapter, msgs, num, true); | |
272 | } | |
273 | ||
0297ffa6 SSM |
274 | static u32 tegra_bpmp_i2c_func(struct i2c_adapter *adapter) |
275 | { | |
276 | return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_10BIT_ADDR | | |
277 | I2C_FUNC_PROTOCOL_MANGLING | I2C_FUNC_NOSTART; | |
278 | } | |
279 | ||
280 | static const struct i2c_algorithm tegra_bpmp_i2c_algo = { | |
281 | .master_xfer = tegra_bpmp_i2c_xfer, | |
08960b02 | 282 | .master_xfer_atomic = tegra_bpmp_i2c_xfer_atomic, |
0297ffa6 SSM |
283 | .functionality = tegra_bpmp_i2c_func, |
284 | }; | |
285 | ||
286 | static int tegra_bpmp_i2c_probe(struct platform_device *pdev) | |
287 | { | |
288 | struct tegra_bpmp_i2c *i2c; | |
289 | u32 value; | |
290 | int err; | |
291 | ||
292 | i2c = devm_kzalloc(&pdev->dev, sizeof(*i2c), GFP_KERNEL); | |
293 | if (!i2c) | |
294 | return -ENOMEM; | |
295 | ||
296 | i2c->dev = &pdev->dev; | |
297 | ||
298 | i2c->bpmp = dev_get_drvdata(pdev->dev.parent); | |
299 | if (!i2c->bpmp) | |
300 | return -ENODEV; | |
301 | ||
302 | err = of_property_read_u32(pdev->dev.of_node, "nvidia,bpmp-bus-id", | |
303 | &value); | |
304 | if (err < 0) | |
305 | return err; | |
306 | ||
307 | i2c->bus = value; | |
308 | ||
309 | i2c_set_adapdata(&i2c->adapter, i2c); | |
310 | i2c->adapter.owner = THIS_MODULE; | |
311 | strlcpy(i2c->adapter.name, "Tegra BPMP I2C adapter", | |
312 | sizeof(i2c->adapter.name)); | |
313 | i2c->adapter.algo = &tegra_bpmp_i2c_algo; | |
314 | i2c->adapter.dev.parent = &pdev->dev; | |
315 | i2c->adapter.dev.of_node = pdev->dev.of_node; | |
316 | ||
317 | platform_set_drvdata(pdev, i2c); | |
318 | ||
319 | return i2c_add_adapter(&i2c->adapter); | |
320 | } | |
321 | ||
322 | static int tegra_bpmp_i2c_remove(struct platform_device *pdev) | |
323 | { | |
324 | struct tegra_bpmp_i2c *i2c = platform_get_drvdata(pdev); | |
325 | ||
326 | i2c_del_adapter(&i2c->adapter); | |
327 | ||
328 | return 0; | |
329 | } | |
330 | ||
331 | static const struct of_device_id tegra_bpmp_i2c_of_match[] = { | |
332 | { .compatible = "nvidia,tegra186-bpmp-i2c", }, | |
333 | { } | |
334 | }; | |
335 | MODULE_DEVICE_TABLE(of, tegra_bpmp_i2c_of_match); | |
336 | ||
337 | static struct platform_driver tegra_bpmp_i2c_driver = { | |
338 | .driver = { | |
339 | .name = "tegra-bpmp-i2c", | |
340 | .of_match_table = tegra_bpmp_i2c_of_match, | |
341 | }, | |
342 | .probe = tegra_bpmp_i2c_probe, | |
343 | .remove = tegra_bpmp_i2c_remove, | |
344 | }; | |
345 | module_platform_driver(tegra_bpmp_i2c_driver); | |
346 | ||
8c91fd5e | 347 | MODULE_DESCRIPTION("NVIDIA Tegra BPMP I2C bus controller driver"); |
0297ffa6 SSM |
348 | MODULE_AUTHOR("Shardar Shariff Md <smohammed@nvidia.com>"); |
349 | MODULE_AUTHOR("Juha-Matti Tilli"); | |
350 | MODULE_LICENSE("GPL v2"); |