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