summaryrefslogtreecommitdiff
path: root/drivers/net/pse-pd/realtek-pse-mcu-i2c.c
blob: b6769ac1d47078c2511792d4ab72ec800f5d89c6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// SPDX-License-Identifier: GPL-2.0-or-later

#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pse-pd/pse.h>

#include "realtek-pse-mcu.h"

/*
 * The core has already waited RTPSE_MCU_RESPONSE_MS before calling us, so
 * the response is normally ready on the very first read. For commands the
 * MCU produces more slowly, keep polling at the typical response cadence
 * up to the worst-case ceiling.
 */
#define RTPSE_MCU_I2C_RETRY_MS	RTPSE_MCU_RESPONSE_MS
#define RTPSE_MCU_I2C_MAX_TRIES	(RTPSE_MCU_RESPONSE_MAX_MS / RTPSE_MCU_I2C_RETRY_MS)

static int rtpse_mcu_i2c_smbus_send(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req)
{
	struct i2c_client *client = to_i2c_client(pse->dev);

	/* Send opcode as SMBus command byte; remaining 11 bytes as block data */
	return i2c_smbus_write_i2c_block_data(client, req->opcode, RTPSE_MCU_MSG_SIZE - 1,
					      (const u8 *)req + 1);
}

static int rtpse_mcu_i2c_smbus_recv(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req,
				    struct rtpse_mcu_msg *resp)
{
	struct i2c_client *client = to_i2c_client(pse->dev);
	int tries, ret;

	for (tries = 0; tries < RTPSE_MCU_I2C_MAX_TRIES; tries++) {
		if (tries > 0)
			msleep(RTPSE_MCU_I2C_RETRY_MS);

		/* MCU needs 0x00 as command byte for read */
		ret = i2c_smbus_read_i2c_block_data(client, 0x00,
						    RTPSE_MCU_MSG_SIZE,
						    (u8 *)resp);
		if (ret < 0)
			return ret;
		if (ret == RTPSE_MCU_MSG_SIZE && rtpse_mcu_resp_is_final(req, resp))
			return 0;
	}

	return -ETIMEDOUT;
}

static const struct rtpse_mcu_transport_ops rtpse_mcu_i2c_smbus_ops = {
	.send = rtpse_mcu_i2c_smbus_send,
	.recv = rtpse_mcu_i2c_smbus_recv,
};

static int rtpse_mcu_i2c_native_send(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req)
{
	struct i2c_client *client = to_i2c_client(pse->dev);
	int ret;

	ret = i2c_master_send(client, (const u8 *)req, RTPSE_MCU_MSG_SIZE);
	if (ret < 0)
		return ret;
	return ret == RTPSE_MCU_MSG_SIZE ? 0 : -EIO;
}

static int rtpse_mcu_i2c_native_recv(struct rtpse_mcu_ctrl *pse, const struct rtpse_mcu_msg *req,
				     struct rtpse_mcu_msg *resp)
{
	struct i2c_client *client = to_i2c_client(pse->dev);
	int tries, ret;

	for (tries = 0; tries < RTPSE_MCU_I2C_MAX_TRIES; tries++) {
		if (tries > 0)
			msleep(RTPSE_MCU_I2C_RETRY_MS);

		ret = i2c_master_recv(client, (u8 *)resp, RTPSE_MCU_MSG_SIZE);
		if (ret < 0)
			return ret;
		if (ret == RTPSE_MCU_MSG_SIZE && rtpse_mcu_resp_is_final(req, resp))
			return 0;
	}

	return -ETIMEDOUT;
}

static const struct rtpse_mcu_transport_ops rtpse_mcu_i2c_native_ops = {
	.send = rtpse_mcu_i2c_native_send,
	.recv = rtpse_mcu_i2c_native_recv,
};

static int rtpse_mcu_i2c_probe(struct i2c_client *client)
{
	struct device *dev = &client->dev;
	const struct rtpse_mcu_match_data *match;
	struct rtpse_mcu_ctrl *pse;
	bool use_native;

	match = device_get_match_data(dev);
	if (!match)
		return dev_err_probe(dev, -ENODEV, "missing match data\n");

	/* The framing (raw I2C vs SMBus) is carried by the match data. */
	use_native = match->native_i2c;
	if (use_native) {
		if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
			return dev_err_probe(dev, -EOPNOTSUPP,
				"plain-I2C MCU protocol requires I2C-capable adapter\n");
	} else {
		if (!i2c_check_functionality(client->adapter,
					     I2C_FUNC_SMBUS_WRITE_I2C_BLOCK |
					     I2C_FUNC_SMBUS_READ_I2C_BLOCK))
			return dev_err_probe(dev, -EOPNOTSUPP,
				"SMBus MCU protocol requires SMBus I2C-block support\n");
	}

	pse = devm_kzalloc(dev, sizeof(*pse), GFP_KERNEL);
	if (!pse)
		return -ENOMEM;

	pse->dev = dev;
	pse->pcdev.owner = THIS_MODULE;
	pse->transport = use_native ? &rtpse_mcu_i2c_native_ops : &rtpse_mcu_i2c_smbus_ops;

	return rtpse_mcu_register(pse);
}

static const struct of_device_id rtpse_mcu_i2c_of_match[] = {
	{ .compatible = "realtek,pse-mcu-gen1-smbus", .data = &rtpse_mcu_gen1_data },
	{ .compatible = "realtek,pse-mcu-gen2-smbus", .data = &rtpse_mcu_gen2_data },
	{ .compatible = "realtek,pse-mcu-gen2-i2c", .data = &rtpse_mcu_gen2_i2c_data },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rtpse_mcu_i2c_of_match);

static struct i2c_driver rtpse_mcu_i2c_driver = {
	.driver = {
		.name		= "realtek-pse-mcu-i2c",
		.of_match_table	= rtpse_mcu_i2c_of_match,
	},
	.probe		= rtpse_mcu_i2c_probe,
};
module_i2c_driver(rtpse_mcu_i2c_driver);

MODULE_AUTHOR("Jonas Jelonek <jelonek.jonas@gmail.com>");
MODULE_DESCRIPTION("Realtek PSE MCU driver (I2C transport)");
MODULE_LICENSE("GPL");