|
| 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"); |
0 commit comments