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 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 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 */ 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 */ 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 */ 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 */ 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