2 * drivers/i2c/busses/i2c-tegra-bpmp.c
4 * Copyright (c) 2016 NVIDIA Corporation. All rights reserved.
6 * Author: Shardar Shariff Md <smohammed@nvidia.com>
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms and conditions of the GNU General Public License,
10 * version 2, as published by the Free Software Foundation.
12 * This program is distributed in the hope it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include <linux/err.h>
22 #include <linux/i2c.h>
23 #include <linux/init.h>
24 #include <linux/kernel.h>
25 #include <linux/module.h>
26 #include <linux/of_device.h>
27 #include <linux/platform_device.h>
28 #include <linux/pm_runtime.h>
30 #include <soc/tegra/bpmp-abi.h>
31 #include <soc/tegra/bpmp.h>
34 * Serialized I2C message header size is 6 bytes and includes address, flags
37 #define SERIALI2C_HDR_SIZE 6
39 struct tegra_bpmp_i2c
{
40 struct i2c_adapter adapter
;
43 struct tegra_bpmp
*bpmp
;
48 * Linux flags are translated to BPMP defined I2C flags that are used in BPMP
49 * firmware I2C driver to avoid any issues in future if Linux I2C flags are
52 static int tegra_bpmp_xlate_flags(u16 flags
, u16
*out
)
54 if (flags
& I2C_M_TEN
) {
55 *out
|= SERIALI2C_TEN
;
59 if (flags
& I2C_M_RD
) {
64 if (flags
& I2C_M_STOP
) {
65 *out
|= SERIALI2C_STOP
;
69 if (flags
& I2C_M_NOSTART
) {
70 *out
|= SERIALI2C_NOSTART
;
71 flags
&= ~I2C_M_NOSTART
;
74 if (flags
& I2C_M_REV_DIR_ADDR
) {
75 *out
|= SERIALI2C_REV_DIR_ADDR
;
76 flags
&= ~I2C_M_REV_DIR_ADDR
;
79 if (flags
& I2C_M_IGNORE_NAK
) {
80 *out
|= SERIALI2C_IGNORE_NAK
;
81 flags
&= ~I2C_M_IGNORE_NAK
;
84 if (flags
& I2C_M_NO_RD_ACK
) {
85 *out
|= SERIALI2C_NO_RD_ACK
;
86 flags
&= ~I2C_M_NO_RD_ACK
;
89 if (flags
& I2C_M_RECV_LEN
) {
90 *out
|= SERIALI2C_RECV_LEN
;
91 flags
&= ~I2C_M_RECV_LEN
;
94 return (flags
!= 0) ? -EINVAL
: 0;
98 * The serialized I2C format is simply the following:
99 * [addr little-endian][flags little-endian][len little-endian][data if write]
100 * [addr little-endian][flags little-endian][len little-endian][data if write]
103 * The flags are translated from Linux kernel representation to seriali2c
104 * representation. Any undefined flag being set causes an error.
106 * The data is there only for writes. Reads have the data transferred in the
107 * other direction, and thus data is not present.
109 * See deserialize_i2c documentation for the data format in the other direction.
111 static int tegra_bpmp_serialize_i2c_msg(struct tegra_bpmp_i2c
*i2c
,
112 struct mrq_i2c_request
*request
,
113 struct i2c_msg
*msgs
,
116 char *buf
= request
->xfer
.data_buf
;
117 unsigned int i
, j
, pos
= 0;
120 for (i
= 0; i
< num
; i
++) {
121 struct i2c_msg
*msg
= &msgs
[i
];
124 err
= tegra_bpmp_xlate_flags(msg
->flags
, &flags
);
128 buf
[pos
++] = msg
->addr
& 0xff;
129 buf
[pos
++] = (msg
->addr
& 0xff00) >> 8;
130 buf
[pos
++] = flags
& 0xff;
131 buf
[pos
++] = (flags
& 0xff00) >> 8;
132 buf
[pos
++] = msg
->len
& 0xff;
133 buf
[pos
++] = (msg
->len
& 0xff00) >> 8;
135 if ((flags
& SERIALI2C_RD
) == 0) {
136 for (j
= 0; j
< msg
->len
; j
++)
137 buf
[pos
++] = msg
->buf
[j
];
141 request
->xfer
.data_size
= pos
;
147 * The data in the BPMP -> CPU direction is composed of sequential blocks for
148 * those messages that have I2C_M_RD. So, for example, if you have:
150 * - !I2C_M_RD, len == 5, data == a0 01 02 03 04
151 * - !I2C_M_RD, len == 1, data == a0
152 * - I2C_M_RD, len == 2, data == [uninitialized buffer 1]
153 * - !I2C_M_RD, len == 1, data == a2
154 * - I2C_M_RD, len == 2, data == [uninitialized buffer 2]
156 * ...then the data in the BPMP -> CPU direction would be 4 bytes total, and
157 * would contain 2 bytes that will go to uninitialized buffer 1, and 2 bytes
158 * that will go to uninitialized buffer 2.
160 static int tegra_bpmp_i2c_deserialize(struct tegra_bpmp_i2c
*i2c
,
161 struct mrq_i2c_response
*response
,
162 struct i2c_msg
*msgs
,
165 size_t size
= response
->xfer
.data_size
, len
= 0, pos
= 0;
166 char *buf
= response
->xfer
.data_buf
;
169 for (i
= 0; i
< num
; i
++)
170 if (msgs
[i
].flags
& I2C_M_RD
)
176 for (i
= 0; i
< num
; i
++) {
177 if (msgs
[i
].flags
& I2C_M_RD
) {
178 memcpy(msgs
[i
].buf
, buf
+ pos
, msgs
[i
].len
);
186 static int tegra_bpmp_i2c_msg_len_check(struct i2c_msg
*msgs
, unsigned int num
)
188 size_t tx_len
= 0, rx_len
= 0;
191 for (i
= 0; i
< num
; i
++)
192 if (!(msgs
[i
].flags
& I2C_M_RD
))
193 tx_len
+= SERIALI2C_HDR_SIZE
+ msgs
[i
].len
;
195 if (tx_len
> TEGRA_I2C_IPC_MAX_IN_BUF_SIZE
)
198 for (i
= 0; i
< num
; i
++)
199 if ((msgs
[i
].flags
& I2C_M_RD
))
200 rx_len
+= msgs
[i
].len
;
202 if (rx_len
> TEGRA_I2C_IPC_MAX_OUT_BUF_SIZE
)
208 static int tegra_bpmp_i2c_msg_xfer(struct tegra_bpmp_i2c
*i2c
,
209 struct mrq_i2c_request
*request
,
210 struct mrq_i2c_response
*response
)
212 struct tegra_bpmp_message msg
;
215 request
->cmd
= CMD_I2C_XFER
;
216 request
->xfer
.bus_id
= i2c
->bus
;
218 memset(&msg
, 0, sizeof(msg
));
220 msg
.tx
.data
= request
;
221 msg
.tx
.size
= sizeof(*request
);
222 msg
.rx
.data
= response
;
223 msg
.rx
.size
= sizeof(*response
);
226 err
= tegra_bpmp_transfer_atomic(i2c
->bpmp
, &msg
);
228 err
= tegra_bpmp_transfer(i2c
->bpmp
, &msg
);
233 static int tegra_bpmp_i2c_xfer(struct i2c_adapter
*adapter
,
234 struct i2c_msg
*msgs
, int num
)
236 struct tegra_bpmp_i2c
*i2c
= i2c_get_adapdata(adapter
);
237 struct mrq_i2c_response response
;
238 struct mrq_i2c_request request
;
241 err
= tegra_bpmp_i2c_msg_len_check(msgs
, num
);
243 dev_err(i2c
->dev
, "unsupported message length\n");
247 memset(&request
, 0, sizeof(request
));
248 memset(&response
, 0, sizeof(response
));
250 err
= tegra_bpmp_serialize_i2c_msg(i2c
, &request
, msgs
, num
);
252 dev_err(i2c
->dev
, "failed to serialize message: %d\n", err
);
256 err
= tegra_bpmp_i2c_msg_xfer(i2c
, &request
, &response
);
258 dev_err(i2c
->dev
, "failed to transfer message: %d\n", err
);
262 err
= tegra_bpmp_i2c_deserialize(i2c
, &response
, msgs
, num
);
264 dev_err(i2c
->dev
, "failed to deserialize message: %d\n", err
);
271 static u32
tegra_bpmp_i2c_func(struct i2c_adapter
*adapter
)
273 return I2C_FUNC_I2C
| I2C_FUNC_SMBUS_EMUL
| I2C_FUNC_10BIT_ADDR
|
274 I2C_FUNC_PROTOCOL_MANGLING
| I2C_FUNC_NOSTART
;
277 static const struct i2c_algorithm tegra_bpmp_i2c_algo
= {
278 .master_xfer
= tegra_bpmp_i2c_xfer
,
279 .functionality
= tegra_bpmp_i2c_func
,
282 static int tegra_bpmp_i2c_probe(struct platform_device
*pdev
)
284 struct tegra_bpmp_i2c
*i2c
;
288 i2c
= devm_kzalloc(&pdev
->dev
, sizeof(*i2c
), GFP_KERNEL
);
292 i2c
->dev
= &pdev
->dev
;
294 i2c
->bpmp
= dev_get_drvdata(pdev
->dev
.parent
);
298 err
= of_property_read_u32(pdev
->dev
.of_node
, "nvidia,bpmp-bus-id",
305 i2c_set_adapdata(&i2c
->adapter
, i2c
);
306 i2c
->adapter
.owner
= THIS_MODULE
;
307 strlcpy(i2c
->adapter
.name
, "Tegra BPMP I2C adapter",
308 sizeof(i2c
->adapter
.name
));
309 i2c
->adapter
.algo
= &tegra_bpmp_i2c_algo
;
310 i2c
->adapter
.dev
.parent
= &pdev
->dev
;
311 i2c
->adapter
.dev
.of_node
= pdev
->dev
.of_node
;
313 platform_set_drvdata(pdev
, i2c
);
315 return i2c_add_adapter(&i2c
->adapter
);
318 static int tegra_bpmp_i2c_remove(struct platform_device
*pdev
)
320 struct tegra_bpmp_i2c
*i2c
= platform_get_drvdata(pdev
);
322 i2c_del_adapter(&i2c
->adapter
);
327 static const struct of_device_id tegra_bpmp_i2c_of_match
[] = {
328 { .compatible
= "nvidia,tegra186-bpmp-i2c", },
331 MODULE_DEVICE_TABLE(of
, tegra_bpmp_i2c_of_match
);
333 static struct platform_driver tegra_bpmp_i2c_driver
= {
335 .name
= "tegra-bpmp-i2c",
336 .of_match_table
= tegra_bpmp_i2c_of_match
,
338 .probe
= tegra_bpmp_i2c_probe
,
339 .remove
= tegra_bpmp_i2c_remove
,
341 module_platform_driver(tegra_bpmp_i2c_driver
);
343 MODULE_DESCRIPTION("NVIDIA Tegra BPMP I2C bus controller driver");
344 MODULE_AUTHOR("Shardar Shariff Md <smohammed@nvidia.com>");
345 MODULE_AUTHOR("Juha-Matti Tilli");
346 MODULE_LICENSE("GPL v2");