1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Copyright (C) 2021 Alibaba Group Holding Limited.
4 * Copyright (c) 2024 Samsung Electronics Co., Ltd.
5 * Author: Michal Wilczynski <m.wilczynski@samsung.com>
6 */
7
8 #include <linux/device.h>
9 #include <linux/firmware/thead/thead,th1520-aon.h>
10 #include <linux/mailbox_client.h>
11 #include <linux/mailbox_controller.h>
12 #include <linux/slab.h>
13
14 #define MAX_RX_TIMEOUT (msecs_to_jiffies(3000))
15 #define MAX_TX_TIMEOUT 500
16
17 struct th1520_aon_chan {
18 struct mbox_chan *ch;
19 struct th1520_aon_rpc_ack_common ack_msg;
20 struct mbox_client cl;
21 struct completion done;
22
23 /* make sure only one RPC is performed at a time */
24 struct mutex transaction_lock;
25 };
26
27 struct th1520_aon_msg_req_set_resource_power_mode {
28 struct th1520_aon_rpc_msg_hdr hdr;
29 u16 resource;
30 u16 mode;
31 u16 reserved[10];
32 } __packed __aligned(1);
33
34 /*
35 * This type is used to indicate error response for most functions.
36 */
37 enum th1520_aon_error_codes {
38 LIGHT_AON_ERR_NONE = 0, /* Success */
39 LIGHT_AON_ERR_VERSION = 1, /* Incompatible API version */
40 LIGHT_AON_ERR_CONFIG = 2, /* Configuration error */
41 LIGHT_AON_ERR_PARM = 3, /* Bad parameter */
42 LIGHT_AON_ERR_NOACCESS = 4, /* Permission error (no access) */
43 LIGHT_AON_ERR_LOCKED = 5, /* Permission error (locked) */
44 LIGHT_AON_ERR_UNAVAILABLE = 6, /* Unavailable (out of resources) */
45 LIGHT_AON_ERR_NOTFOUND = 7, /* Not found */
46 LIGHT_AON_ERR_NOPOWER = 8, /* No power */
47 LIGHT_AON_ERR_IPC = 9, /* Generic IPC error */
48 LIGHT_AON_ERR_BUSY = 10, /* Resource is currently busy/active */
49 LIGHT_AON_ERR_FAIL = 11, /* General I/O failure */
50 LIGHT_AON_ERR_LAST
51 };
52
53 static int th1520_aon_linux_errmap[LIGHT_AON_ERR_LAST] = {
54 0, /* LIGHT_AON_ERR_NONE */
55 -EINVAL, /* LIGHT_AON_ERR_VERSION */
56 -EINVAL, /* LIGHT_AON_ERR_CONFIG */
57 -EINVAL, /* LIGHT_AON_ERR_PARM */
58 -EACCES, /* LIGHT_AON_ERR_NOACCESS */
59 -EACCES, /* LIGHT_AON_ERR_LOCKED */
60 -ERANGE, /* LIGHT_AON_ERR_UNAVAILABLE */
61 -EEXIST, /* LIGHT_AON_ERR_NOTFOUND */
62 -EPERM, /* LIGHT_AON_ERR_NOPOWER */
63 -EPIPE, /* LIGHT_AON_ERR_IPC */
64 -EBUSY, /* LIGHT_AON_ERR_BUSY */
65 -EIO, /* LIGHT_AON_ERR_FAIL */
66 };
67
th1520_aon_to_linux_errno(int errno)68 static inline int th1520_aon_to_linux_errno(int errno)
69 {
70 if (errno >= LIGHT_AON_ERR_NONE && errno < LIGHT_AON_ERR_LAST)
71 return th1520_aon_linux_errmap[errno];
72
73 return -EIO;
74 }
75
th1520_aon_rx_callback(struct mbox_client * c,void * rx_msg)76 static void th1520_aon_rx_callback(struct mbox_client *c, void *rx_msg)
77 {
78 struct th1520_aon_chan *aon_chan =
79 container_of(c, struct th1520_aon_chan, cl);
80 struct th1520_aon_rpc_msg_hdr *hdr =
81 (struct th1520_aon_rpc_msg_hdr *)rx_msg;
82 u8 recv_size = sizeof(struct th1520_aon_rpc_msg_hdr) + hdr->size;
83
84 if (recv_size != sizeof(struct th1520_aon_rpc_ack_common)) {
85 dev_err(c->dev, "Invalid ack size, not completing\n");
86 return;
87 }
88
89 memcpy(&aon_chan->ack_msg, rx_msg, recv_size);
90 complete(&aon_chan->done);
91 }
92
93 /**
94 * th1520_aon_call_rpc() - Send an RPC request to the TH1520 AON subsystem
95 * @aon_chan: Pointer to the AON channel structure
96 * @msg: Pointer to the message (RPC payload) that will be sent
97 *
98 * This function sends an RPC message to the TH1520 AON subsystem via mailbox.
99 * It takes the provided @msg buffer, formats it with version and service flags,
100 * then blocks until the RPC completes or times out. The completion is signaled
101 * by the `aon_chan->done` completion, which is waited upon for a duration
102 * defined by `MAX_RX_TIMEOUT`.
103 *
104 * Return:
105 * * 0 on success
106 * * -ETIMEDOUT if the RPC call times out
107 * * A negative error code if the mailbox send fails or if AON responds with
108 * a non-zero error code (converted via th1520_aon_to_linux_errno()).
109 */
th1520_aon_call_rpc(struct th1520_aon_chan * aon_chan,void * msg)110 int th1520_aon_call_rpc(struct th1520_aon_chan *aon_chan, void *msg)
111 {
112 struct th1520_aon_rpc_msg_hdr *hdr = msg;
113 int ret;
114
115 mutex_lock(&aon_chan->transaction_lock);
116 reinit_completion(&aon_chan->done);
117
118 RPC_SET_VER(hdr, TH1520_AON_RPC_VERSION);
119 RPC_SET_SVC_ID(hdr, hdr->svc);
120 RPC_SET_SVC_FLAG_MSG_TYPE(hdr, RPC_SVC_MSG_TYPE_DATA);
121 RPC_SET_SVC_FLAG_ACK_TYPE(hdr, RPC_SVC_MSG_NEED_ACK);
122
123 ret = mbox_send_message(aon_chan->ch, msg);
124 if (ret < 0) {
125 dev_err(aon_chan->cl.dev, "RPC send msg failed: %d\n", ret);
126 goto out;
127 }
128
129 if (!wait_for_completion_timeout(&aon_chan->done, MAX_RX_TIMEOUT)) {
130 dev_err(aon_chan->cl.dev, "RPC send msg timeout\n");
131 mutex_unlock(&aon_chan->transaction_lock);
132 return -ETIMEDOUT;
133 }
134
135 ret = aon_chan->ack_msg.err_code;
136
137 out:
138 mutex_unlock(&aon_chan->transaction_lock);
139
140 return th1520_aon_to_linux_errno(ret);
141 }
142 EXPORT_SYMBOL_GPL(th1520_aon_call_rpc);
143
144 /**
145 * th1520_aon_power_update() - Change power state of a resource via TH1520 AON
146 * @aon_chan: Pointer to the AON channel structure
147 * @rsrc: Resource ID whose power state needs to be updated
148 * @power_on: Boolean indicating whether the resource should be powered on (true)
149 * or powered off (false)
150 *
151 * This function requests the TH1520 AON subsystem to set the power mode of the
152 * given resource (@rsrc) to either on or off. It constructs the message in
153 * `struct th1520_aon_msg_req_set_resource_power_mode` and then invokes
154 * th1520_aon_call_rpc() to make the request. If the AON call fails, an error
155 * message is logged along with the specific return code.
156 *
157 * Return:
158 * * 0 on success
159 * * A negative error code in case of failures (propagated from
160 * th1520_aon_call_rpc()).
161 */
th1520_aon_power_update(struct th1520_aon_chan * aon_chan,u16 rsrc,bool power_on)162 int th1520_aon_power_update(struct th1520_aon_chan *aon_chan, u16 rsrc,
163 bool power_on)
164 {
165 struct th1520_aon_msg_req_set_resource_power_mode msg = {};
166 struct th1520_aon_rpc_msg_hdr *hdr = &msg.hdr;
167 int ret;
168
169 hdr->svc = TH1520_AON_RPC_SVC_PM;
170 hdr->func = TH1520_AON_PM_FUNC_SET_RESOURCE_POWER_MODE;
171 hdr->size = TH1520_AON_RPC_MSG_NUM;
172
173 RPC_SET_BE16(&msg.resource, 0, rsrc);
174 RPC_SET_BE16(&msg.resource, 2,
175 (power_on ? TH1520_AON_PM_PW_MODE_ON :
176 TH1520_AON_PM_PW_MODE_OFF));
177
178 ret = th1520_aon_call_rpc(aon_chan, &msg);
179 if (ret)
180 dev_err(aon_chan->cl.dev, "failed to power %s resource %d ret %d\n",
181 power_on ? "up" : "off", rsrc, ret);
182
183 return ret;
184 }
185 EXPORT_SYMBOL_GPL(th1520_aon_power_update);
186
187 /**
188 * th1520_aon_init() - Initialize TH1520 AON firmware protocol interface
189 * @dev: Device pointer for the AON subsystem
190 *
191 * This function initializes the TH1520 AON firmware protocol interface by:
192 * - Allocating and initializing the AON channel structure
193 * - Setting up the mailbox client
194 * - Requesting the AON mailbox channel
195 * - Initializing synchronization primitives
196 *
197 * Return:
198 * * Valid pointer to th1520_aon_chan structure on success
199 * * ERR_PTR(-ENOMEM) if memory allocation fails
200 * * ERR_PTR() with other negative error codes from mailbox operations
201 */
th1520_aon_init(struct device * dev)202 struct th1520_aon_chan *th1520_aon_init(struct device *dev)
203 {
204 struct th1520_aon_chan *aon_chan;
205 struct mbox_client *cl;
206 int ret;
207
208 aon_chan = kzalloc(sizeof(*aon_chan), GFP_KERNEL);
209 if (!aon_chan)
210 return ERR_PTR(-ENOMEM);
211
212 cl = &aon_chan->cl;
213 cl->dev = dev;
214 cl->tx_block = true;
215 cl->tx_tout = MAX_TX_TIMEOUT;
216 cl->rx_callback = th1520_aon_rx_callback;
217
218 aon_chan->ch = mbox_request_channel_byname(cl, "aon");
219 if (IS_ERR(aon_chan->ch)) {
220 dev_err(dev, "Failed to request aon mbox chan\n");
221 ret = PTR_ERR(aon_chan->ch);
222 kfree(aon_chan);
223 return ERR_PTR(ret);
224 }
225
226 mutex_init(&aon_chan->transaction_lock);
227 init_completion(&aon_chan->done);
228
229 return aon_chan;
230 }
231 EXPORT_SYMBOL_GPL(th1520_aon_init);
232
233 /**
234 * th1520_aon_deinit() - Clean up TH1520 AON firmware protocol interface
235 * @aon_chan: Pointer to the AON channel structure to clean up
236 *
237 * This function cleans up resources allocated by th1520_aon_init():
238 * - Frees the mailbox channel
239 * - Frees the AON channel
240 */
th1520_aon_deinit(struct th1520_aon_chan * aon_chan)241 void th1520_aon_deinit(struct th1520_aon_chan *aon_chan)
242 {
243 mbox_free_channel(aon_chan->ch);
244 kfree(aon_chan);
245 }
246 EXPORT_SYMBOL_GPL(th1520_aon_deinit);
247
248 MODULE_AUTHOR("Michal Wilczynski <m.wilczynski@samsung.com>");
249 MODULE_DESCRIPTION("T-HEAD TH1520 Always-On firmware protocol library");
250 MODULE_LICENSE("GPL");
251