summaryrefslogtreecommitdiff
path: root/drivers/firmware/thead,th1520-aon.c
blob: 38f812ac9920e36ec16e058f81cec2ca05d67c25 (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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2021 Alibaba Group Holding Limited.
 * Copyright (c) 2024 Samsung Electronics Co., Ltd.
 * Author: Michal Wilczynski <m.wilczynski@samsung.com>
 */

#include <linux/device.h>
#include <linux/firmware/thead/thead,th1520-aon.h>
#include <linux/mailbox_client.h>
#include <linux/mailbox_controller.h>
#include <linux/slab.h>

#define MAX_RX_TIMEOUT (msecs_to_jiffies(3000))
#define MAX_TX_TIMEOUT 500

struct th1520_aon_chan {
	struct mbox_chan *ch;
	struct th1520_aon_rpc_ack_common ack_msg;
	struct mbox_client cl;
	struct completion done;

	/* make sure only one RPC is performed at a time */
	struct mutex transaction_lock;
};

struct th1520_aon_msg_req_set_resource_power_mode {
	struct th1520_aon_rpc_msg_hdr hdr;
	u16 resource;
	u16 mode;
	u16 reserved[10];
} __packed __aligned(1);

/*
 * This type is used to indicate error response for most functions.
 */
enum th1520_aon_error_codes {
	LIGHT_AON_ERR_NONE = 0, /* Success */
	LIGHT_AON_ERR_VERSION = 1, /* Incompatible API version */
	LIGHT_AON_ERR_CONFIG = 2, /* Configuration error */
	LIGHT_AON_ERR_PARM = 3, /* Bad parameter */
	LIGHT_AON_ERR_NOACCESS = 4, /* Permission error (no access) */
	LIGHT_AON_ERR_LOCKED = 5, /* Permission error (locked) */
	LIGHT_AON_ERR_UNAVAILABLE = 6, /* Unavailable (out of resources) */
	LIGHT_AON_ERR_NOTFOUND = 7, /* Not found */
	LIGHT_AON_ERR_NOPOWER = 8, /* No power */
	LIGHT_AON_ERR_IPC = 9, /* Generic IPC error */
	LIGHT_AON_ERR_BUSY = 10, /* Resource is currently busy/active */
	LIGHT_AON_ERR_FAIL = 11, /* General I/O failure */
	LIGHT_AON_ERR_LAST
};

static int th1520_aon_linux_errmap[LIGHT_AON_ERR_LAST] = {
	0, /* LIGHT_AON_ERR_NONE */
	-EINVAL, /* LIGHT_AON_ERR_VERSION */
	-EINVAL, /* LIGHT_AON_ERR_CONFIG */
	-EINVAL, /* LIGHT_AON_ERR_PARM */
	-EACCES, /* LIGHT_AON_ERR_NOACCESS */
	-EACCES, /* LIGHT_AON_ERR_LOCKED */
	-ERANGE, /* LIGHT_AON_ERR_UNAVAILABLE */
	-EEXIST, /* LIGHT_AON_ERR_NOTFOUND */
	-EPERM, /* LIGHT_AON_ERR_NOPOWER */
	-EPIPE, /* LIGHT_AON_ERR_IPC */
	-EBUSY, /* LIGHT_AON_ERR_BUSY */
	-EIO, /* LIGHT_AON_ERR_FAIL */
};

static inline int th1520_aon_to_linux_errno(int errno)
{
	if (errno >= LIGHT_AON_ERR_NONE && errno < LIGHT_AON_ERR_LAST)
		return th1520_aon_linux_errmap[errno];

	return -EIO;
}

static void th1520_aon_rx_callback(struct mbox_client *c, void *rx_msg)
{
	struct th1520_aon_chan *aon_chan =
		container_of(c, struct th1520_aon_chan, cl);
	struct th1520_aon_rpc_msg_hdr *hdr =
		(struct th1520_aon_rpc_msg_hdr *)rx_msg;
	u8 recv_size = sizeof(struct th1520_aon_rpc_msg_hdr) + hdr->size;

	if (recv_size != sizeof(struct th1520_aon_rpc_ack_common)) {
		dev_err(c->dev, "Invalid ack size, not completing\n");
		return;
	}

	memcpy(&aon_chan->ack_msg, rx_msg, recv_size);
	complete(&aon_chan->done);
}

/**
 * th1520_aon_call_rpc() - Send an RPC request to the TH1520 AON subsystem
 * @aon_chan: Pointer to the AON channel structure
 * @msg: Pointer to the message (RPC payload) that will be sent
 *
 * This function sends an RPC message to the TH1520 AON subsystem via mailbox.
 * It takes the provided @msg buffer, formats it with version and service flags,
 * then blocks until the RPC completes or times out. The completion is signaled
 * by the `aon_chan->done` completion, which is waited upon for a duration
 * defined by `MAX_RX_TIMEOUT`.
 *
 * Return:
 * * 0 on success
 * * -ETIMEDOUT if the RPC call times out
 * * A negative error code if the mailbox send fails or if AON responds with
 *   a non-zero error code (converted via th1520_aon_to_linux_errno()).
 */
int th1520_aon_call_rpc(struct th1520_aon_chan *aon_chan, void *msg)
{
	struct th1520_aon_rpc_msg_hdr *hdr = msg;
	int ret;

	mutex_lock(&aon_chan->transaction_lock);
	reinit_completion(&aon_chan->done);

	RPC_SET_VER(hdr, TH1520_AON_RPC_VERSION);
	RPC_SET_SVC_ID(hdr, hdr->svc);
	RPC_SET_SVC_FLAG_MSG_TYPE(hdr, RPC_SVC_MSG_TYPE_DATA);
	RPC_SET_SVC_FLAG_ACK_TYPE(hdr, RPC_SVC_MSG_NEED_ACK);

	ret = mbox_send_message(aon_chan->ch, msg);
	if (ret < 0) {
		dev_err(aon_chan->cl.dev, "RPC send msg failed: %d\n", ret);
		goto out;
	}

	if (!wait_for_completion_timeout(&aon_chan->done, MAX_RX_TIMEOUT)) {
		dev_err(aon_chan->cl.dev, "RPC send msg timeout\n");
		mutex_unlock(&aon_chan->transaction_lock);
		return -ETIMEDOUT;
	}

	ret = aon_chan->ack_msg.err_code;

out:
	mutex_unlock(&aon_chan->transaction_lock);

	return th1520_aon_to_linux_errno(ret);
}
EXPORT_SYMBOL_GPL(th1520_aon_call_rpc);

/**
 * th1520_aon_power_update() - Change power state of a resource via TH1520 AON
 * @aon_chan: Pointer to the AON channel structure
 * @rsrc: Resource ID whose power state needs to be updated
 * @power_on: Boolean indicating whether the resource should be powered on (true)
 *            or powered off (false)
 *
 * This function requests the TH1520 AON subsystem to set the power mode of the
 * given resource (@rsrc) to either on or off. It constructs the message in
 * `struct th1520_aon_msg_req_set_resource_power_mode` and then invokes
 * th1520_aon_call_rpc() to make the request. If the AON call fails, an error
 * message is logged along with the specific return code.
 *
 * Return:
 * * 0 on success
 * * A negative error code in case of failures (propagated from
 *   th1520_aon_call_rpc()).
 */
int th1520_aon_power_update(struct th1520_aon_chan *aon_chan, u16 rsrc,
			    bool power_on)
{
	struct th1520_aon_msg_req_set_resource_power_mode msg = {};
	struct th1520_aon_rpc_msg_hdr *hdr = &msg.hdr;
	int ret;

	hdr->svc = TH1520_AON_RPC_SVC_PM;
	hdr->func = TH1520_AON_PM_FUNC_SET_RESOURCE_POWER_MODE;
	hdr->size = TH1520_AON_RPC_MSG_NUM;

	RPC_SET_BE16(&msg.resource, 0, rsrc);
	RPC_SET_BE16(&msg.resource, 2,
		     (power_on ? TH1520_AON_PM_PW_MODE_ON :
				 TH1520_AON_PM_PW_MODE_OFF));

	ret = th1520_aon_call_rpc(aon_chan, &msg);
	if (ret)
		dev_err(aon_chan->cl.dev, "failed to power %s resource %d ret %d\n",
			power_on ? "up" : "off", rsrc, ret);

	return ret;
}
EXPORT_SYMBOL_GPL(th1520_aon_power_update);

/**
 * th1520_aon_init() - Initialize TH1520 AON firmware protocol interface
 * @dev: Device pointer for the AON subsystem
 *
 * This function initializes the TH1520 AON firmware protocol interface by:
 * - Allocating and initializing the AON channel structure
 * - Setting up the mailbox client
 * - Requesting the AON mailbox channel
 * - Initializing synchronization primitives
 *
 * Return:
 * * Valid pointer to th1520_aon_chan structure on success
 * * ERR_PTR(-ENOMEM) if memory allocation fails
 * * ERR_PTR() with other negative error codes from mailbox operations
 */
struct th1520_aon_chan *th1520_aon_init(struct device *dev)
{
	struct th1520_aon_chan *aon_chan;
	struct mbox_client *cl;
	int ret;

	aon_chan = kzalloc(sizeof(*aon_chan), GFP_KERNEL);
	if (!aon_chan)
		return ERR_PTR(-ENOMEM);

	cl = &aon_chan->cl;
	cl->dev = dev;
	cl->tx_block = true;
	cl->tx_tout = MAX_TX_TIMEOUT;
	cl->rx_callback = th1520_aon_rx_callback;

	aon_chan->ch = mbox_request_channel_byname(cl, "aon");
	if (IS_ERR(aon_chan->ch)) {
		dev_err(dev, "Failed to request aon mbox chan\n");
		ret = PTR_ERR(aon_chan->ch);
		kfree(aon_chan);
		return ERR_PTR(ret);
	}

	mutex_init(&aon_chan->transaction_lock);
	init_completion(&aon_chan->done);

	return aon_chan;
}
EXPORT_SYMBOL_GPL(th1520_aon_init);

/**
 * th1520_aon_deinit() - Clean up TH1520 AON firmware protocol interface
 * @aon_chan: Pointer to the AON channel structure to clean up
 *
 * This function cleans up resources allocated by th1520_aon_init():
 * - Frees the mailbox channel
 * - Frees the AON channel
 */
void th1520_aon_deinit(struct th1520_aon_chan *aon_chan)
{
	mbox_free_channel(aon_chan->ch);
	kfree(aon_chan);
}
EXPORT_SYMBOL_GPL(th1520_aon_deinit);

MODULE_AUTHOR("Michal Wilczynski <m.wilczynski@samsung.com>");
MODULE_DESCRIPTION("T-HEAD TH1520 Always-On firmware protocol library");
MODULE_LICENSE("GPL");