Path: blob/master/drivers/firmware/samsung/exynos-acpm.c
51407 views
// SPDX-License-Identifier: GPL-2.0-only1/*2* Copyright 2020 Samsung Electronics Co., Ltd.3* Copyright 2020 Google LLC.4* Copyright 2024 Linaro Ltd.5*/67#include <linux/bitfield.h>8#include <linux/bitmap.h>9#include <linux/bits.h>10#include <linux/cleanup.h>11#include <linux/container_of.h>12#include <linux/delay.h>13#include <linux/device.h>14#include <linux/firmware/samsung/exynos-acpm-protocol.h>15#include <linux/io.h>16#include <linux/iopoll.h>17#include <linux/ktime.h>18#include <linux/mailbox/exynos-message.h>19#include <linux/mailbox_client.h>20#include <linux/module.h>21#include <linux/mutex.h>22#include <linux/math.h>23#include <linux/of.h>24#include <linux/of_address.h>25#include <linux/of_platform.h>26#include <linux/platform_device.h>27#include <linux/slab.h>28#include <linux/types.h>2930#include "exynos-acpm.h"31#include "exynos-acpm-dvfs.h"32#include "exynos-acpm-pmic.h"3334#define ACPM_PROTOCOL_SEQNUM GENMASK(21, 16)3536#define ACPM_POLL_TIMEOUT_US (100 * USEC_PER_MSEC)37#define ACPM_TX_TIMEOUT_US 5000003839#define ACPM_GS101_INITDATA_BASE 0xa0004041/**42* struct acpm_shmem - shared memory configuration information.43* @reserved: unused fields.44* @chans: offset to array of struct acpm_chan_shmem.45* @reserved1: unused fields.46* @num_chans: number of channels.47*/48struct acpm_shmem {49u32 reserved[2];50u32 chans;51u32 reserved1[3];52u32 num_chans;53};5455/**56* struct acpm_chan_shmem - descriptor of a shared memory channel.57*58* @id: channel ID.59* @reserved: unused fields.60* @rx_rear: rear pointer of APM RX queue (TX for AP).61* @rx_front: front pointer of APM RX queue (TX for AP).62* @rx_base: base address of APM RX queue (TX for AP).63* @reserved1: unused fields.64* @tx_rear: rear pointer of APM TX queue (RX for AP).65* @tx_front: front pointer of APM TX queue (RX for AP).66* @tx_base: base address of APM TX queue (RX for AP).67* @qlen: queue length. Applies to both TX/RX queues.68* @mlen: message length. Applies to both TX/RX queues.69* @reserved2: unused fields.70* @poll_completion: true when the channel works on polling.71*/72struct acpm_chan_shmem {73u32 id;74u32 reserved[3];75u32 rx_rear;76u32 rx_front;77u32 rx_base;78u32 reserved1[3];79u32 tx_rear;80u32 tx_front;81u32 tx_base;82u32 qlen;83u32 mlen;84u32 reserved2[2];85u32 poll_completion;86};8788/**89* struct acpm_queue - exynos acpm queue.90*91* @rear: rear address of the queue.92* @front: front address of the queue.93* @base: base address of the queue.94*/95struct acpm_queue {96void __iomem *rear;97void __iomem *front;98void __iomem *base;99};100101/**102* struct acpm_rx_data - RX queue data.103*104* @cmd: pointer to where the data shall be saved.105* @n_cmd: number of 32-bit commands.106* @response: true if the client expects the RX data.107*/108struct acpm_rx_data {109u32 *cmd;110size_t n_cmd;111bool response;112};113114#define ACPM_SEQNUM_MAX 64115116/**117* struct acpm_chan - driver internal representation of a channel.118* @cl: mailbox client.119* @chan: mailbox channel.120* @acpm: pointer to driver private data.121* @tx: TX queue. The enqueue is done by the host.122* - front index is written by the host.123* - rear index is written by the firmware.124*125* @rx: RX queue. The enqueue is done by the firmware.126* - front index is written by the firmware.127* - rear index is written by the host.128* @tx_lock: protects TX queue.129* @rx_lock: protects RX queue.130* @qlen: queue length. Applies to both TX/RX queues.131* @mlen: message length. Applies to both TX/RX queues.132* @seqnum: sequence number of the last message enqueued on TX queue.133* @id: channel ID.134* @poll_completion: indicates if the transfer needs to be polled for135* completion or interrupt mode is used.136* @bitmap_seqnum: bitmap that tracks the messages on the TX/RX queues.137* @rx_data: internal buffer used to drain the RX queue.138*/139struct acpm_chan {140struct mbox_client cl;141struct mbox_chan *chan;142struct acpm_info *acpm;143struct acpm_queue tx;144struct acpm_queue rx;145struct mutex tx_lock;146struct mutex rx_lock;147148unsigned int qlen;149unsigned int mlen;150u8 seqnum;151u8 id;152bool poll_completion;153154DECLARE_BITMAP(bitmap_seqnum, ACPM_SEQNUM_MAX - 1);155struct acpm_rx_data rx_data[ACPM_SEQNUM_MAX];156};157158/**159* struct acpm_info - driver's private data.160* @shmem: pointer to the SRAM configuration data.161* @sram_base: base address of SRAM.162* @chans: pointer to the ACPM channel parameters retrieved from SRAM.163* @dev: pointer to the exynos-acpm device.164* @handle: instance of acpm_handle to send to clients.165* @num_chans: number of channels available for this controller.166*/167struct acpm_info {168struct acpm_shmem __iomem *shmem;169void __iomem *sram_base;170struct acpm_chan *chans;171struct device *dev;172struct acpm_handle handle;173u32 num_chans;174};175176/**177* struct acpm_match_data - of_device_id data.178* @initdata_base: offset in SRAM where the channels configuration resides.179* @acpm_clk_dev_name: base name for the ACPM clocks device that we're registering.180*/181struct acpm_match_data {182loff_t initdata_base;183const char *acpm_clk_dev_name;184};185186#define client_to_acpm_chan(c) container_of(c, struct acpm_chan, cl)187#define handle_to_acpm_info(h) container_of(h, struct acpm_info, handle)188189/**190* acpm_get_saved_rx() - get the response if it was already saved.191* @achan: ACPM channel info.192* @xfer: reference to the transfer to get response for.193* @tx_seqnum: xfer TX sequence number.194*/195static void acpm_get_saved_rx(struct acpm_chan *achan,196const struct acpm_xfer *xfer, u32 tx_seqnum)197{198const struct acpm_rx_data *rx_data = &achan->rx_data[tx_seqnum - 1];199u32 rx_seqnum;200201if (!rx_data->response)202return;203204rx_seqnum = FIELD_GET(ACPM_PROTOCOL_SEQNUM, rx_data->cmd[0]);205206if (rx_seqnum == tx_seqnum) {207memcpy(xfer->rxd, rx_data->cmd, xfer->rxlen);208clear_bit(rx_seqnum - 1, achan->bitmap_seqnum);209}210}211212/**213* acpm_get_rx() - get response from RX queue.214* @achan: ACPM channel info.215* @xfer: reference to the transfer to get response for.216*217* Return: 0 on success, -errno otherwise.218*/219static int acpm_get_rx(struct acpm_chan *achan, const struct acpm_xfer *xfer)220{221u32 rx_front, rx_seqnum, tx_seqnum, seqnum;222const void __iomem *base, *addr;223struct acpm_rx_data *rx_data;224u32 i, val, mlen;225bool rx_set = false;226227guard(mutex)(&achan->rx_lock);228229rx_front = readl(achan->rx.front);230i = readl(achan->rx.rear);231232tx_seqnum = FIELD_GET(ACPM_PROTOCOL_SEQNUM, xfer->txd[0]);233234if (i == rx_front) {235acpm_get_saved_rx(achan, xfer, tx_seqnum);236return 0;237}238239base = achan->rx.base;240mlen = achan->mlen;241242/* Drain RX queue. */243do {244/* Read RX seqnum. */245addr = base + mlen * i;246val = readl(addr);247248rx_seqnum = FIELD_GET(ACPM_PROTOCOL_SEQNUM, val);249if (!rx_seqnum)250return -EIO;251/*252* mssg seqnum starts with value 1, whereas the driver considers253* the first mssg at index 0.254*/255seqnum = rx_seqnum - 1;256rx_data = &achan->rx_data[seqnum];257258if (rx_data->response) {259if (rx_seqnum == tx_seqnum) {260__ioread32_copy(xfer->rxd, addr,261xfer->rxlen / 4);262rx_set = true;263clear_bit(seqnum, achan->bitmap_seqnum);264} else {265/*266* The RX data corresponds to another request.267* Save the data to drain the queue, but don't268* clear yet the bitmap. It will be cleared269* after the response is copied to the request.270*/271__ioread32_copy(rx_data->cmd, addr,272xfer->rxlen / 4);273}274} else {275clear_bit(seqnum, achan->bitmap_seqnum);276}277278i = (i + 1) % achan->qlen;279} while (i != rx_front);280281/* We saved all responses, mark RX empty. */282writel(rx_front, achan->rx.rear);283284/*285* If the response was not in this iteration of the queue, check if the286* RX data was previously saved.287*/288if (!rx_set)289acpm_get_saved_rx(achan, xfer, tx_seqnum);290291return 0;292}293294/**295* acpm_dequeue_by_polling() - RX dequeue by polling.296* @achan: ACPM channel info.297* @xfer: reference to the transfer being waited for.298*299* Return: 0 on success, -errno otherwise.300*/301static int acpm_dequeue_by_polling(struct acpm_chan *achan,302const struct acpm_xfer *xfer)303{304struct device *dev = achan->acpm->dev;305ktime_t timeout;306u32 seqnum;307int ret;308309seqnum = FIELD_GET(ACPM_PROTOCOL_SEQNUM, xfer->txd[0]);310311timeout = ktime_add_us(ktime_get(), ACPM_POLL_TIMEOUT_US);312do {313ret = acpm_get_rx(achan, xfer);314if (ret)315return ret;316317if (!test_bit(seqnum - 1, achan->bitmap_seqnum))318return 0;319320/* Determined experimentally. */321udelay(20);322} while (ktime_before(ktime_get(), timeout));323324dev_err(dev, "Timeout! ch:%u s:%u bitmap:%lx.\n",325achan->id, seqnum, achan->bitmap_seqnum[0]);326327return -ETIME;328}329330/**331* acpm_wait_for_queue_slots() - wait for queue slots.332*333* @achan: ACPM channel info.334* @next_tx_front: next front index of the TX queue.335*336* Return: 0 on success, -errno otherwise.337*/338static int acpm_wait_for_queue_slots(struct acpm_chan *achan, u32 next_tx_front)339{340u32 val, ret;341342/*343* Wait for RX front to keep up with TX front. Make sure there's at344* least one element between them.345*/346ret = readl_poll_timeout(achan->rx.front, val, next_tx_front != val, 0,347ACPM_TX_TIMEOUT_US);348if (ret) {349dev_err(achan->acpm->dev, "RX front can not keep up with TX front.\n");350return ret;351}352353ret = readl_poll_timeout(achan->tx.rear, val, next_tx_front != val, 0,354ACPM_TX_TIMEOUT_US);355if (ret)356dev_err(achan->acpm->dev, "TX queue is full.\n");357358return ret;359}360361/**362* acpm_prepare_xfer() - prepare a transfer before writing the message to the363* TX queue.364* @achan: ACPM channel info.365* @xfer: reference to the transfer being prepared.366*/367static void acpm_prepare_xfer(struct acpm_chan *achan,368const struct acpm_xfer *xfer)369{370struct acpm_rx_data *rx_data;371u32 *txd = (u32 *)xfer->txd;372373/* Prevent chan->seqnum from being re-used */374do {375if (++achan->seqnum == ACPM_SEQNUM_MAX)376achan->seqnum = 1;377} while (test_bit(achan->seqnum - 1, achan->bitmap_seqnum));378379txd[0] |= FIELD_PREP(ACPM_PROTOCOL_SEQNUM, achan->seqnum);380381/* Clear data for upcoming responses */382rx_data = &achan->rx_data[achan->seqnum - 1];383memset(rx_data->cmd, 0, sizeof(*rx_data->cmd) * rx_data->n_cmd);384if (xfer->rxd)385rx_data->response = true;386387/* Flag the index based on seqnum. (seqnum: 1~63, bitmap: 0~62) */388set_bit(achan->seqnum - 1, achan->bitmap_seqnum);389}390391/**392* acpm_wait_for_message_response - an helper to group all possible ways of393* waiting for a synchronous message response.394*395* @achan: ACPM channel info.396* @xfer: reference to the transfer being waited for.397*398* Return: 0 on success, -errno otherwise.399*/400static int acpm_wait_for_message_response(struct acpm_chan *achan,401const struct acpm_xfer *xfer)402{403/* Just polling mode supported for now. */404return acpm_dequeue_by_polling(achan, xfer);405}406407/**408* acpm_do_xfer() - do one transfer.409* @handle: pointer to the acpm handle.410* @xfer: transfer to initiate and wait for response.411*412* Return: 0 on success, -errno otherwise.413*/414int acpm_do_xfer(const struct acpm_handle *handle, const struct acpm_xfer *xfer)415{416struct acpm_info *acpm = handle_to_acpm_info(handle);417struct exynos_mbox_msg msg;418struct acpm_chan *achan;419u32 idx, tx_front;420int ret;421422if (xfer->acpm_chan_id >= acpm->num_chans)423return -EINVAL;424425achan = &acpm->chans[xfer->acpm_chan_id];426427if (!xfer->txd || xfer->txlen > achan->mlen || xfer->rxlen > achan->mlen)428return -EINVAL;429430if (!achan->poll_completion) {431dev_err(achan->acpm->dev, "Interrupt mode not supported\n");432return -EOPNOTSUPP;433}434435msg.chan_id = xfer->acpm_chan_id;436msg.chan_type = EXYNOS_MBOX_CHAN_TYPE_DOORBELL;437438scoped_guard(mutex, &achan->tx_lock) {439tx_front = readl(achan->tx.front);440idx = (tx_front + 1) % achan->qlen;441442ret = acpm_wait_for_queue_slots(achan, idx);443if (ret)444return ret;445446acpm_prepare_xfer(achan, xfer);447448/* Write TX command. */449__iowrite32_copy(achan->tx.base + achan->mlen * tx_front,450xfer->txd, xfer->txlen / 4);451452/* Advance TX front. */453writel(idx, achan->tx.front);454455ret = mbox_send_message(achan->chan, (void *)&msg);456if (ret < 0)457return ret;458459mbox_client_txdone(achan->chan, 0);460}461462return acpm_wait_for_message_response(achan, xfer);463}464465/**466* acpm_chan_shmem_get_params() - get channel parameters and addresses of the467* TX/RX queues.468* @achan: ACPM channel info.469* @chan_shmem: __iomem pointer to a channel described in shared memory.470*/471static void acpm_chan_shmem_get_params(struct acpm_chan *achan,472struct acpm_chan_shmem __iomem *chan_shmem)473{474void __iomem *base = achan->acpm->sram_base;475struct acpm_queue *rx = &achan->rx;476struct acpm_queue *tx = &achan->tx;477478achan->mlen = readl(&chan_shmem->mlen);479achan->poll_completion = readl(&chan_shmem->poll_completion);480achan->id = readl(&chan_shmem->id);481achan->qlen = readl(&chan_shmem->qlen);482483tx->base = base + readl(&chan_shmem->rx_base);484tx->rear = base + readl(&chan_shmem->rx_rear);485tx->front = base + readl(&chan_shmem->rx_front);486487rx->base = base + readl(&chan_shmem->tx_base);488rx->rear = base + readl(&chan_shmem->tx_rear);489rx->front = base + readl(&chan_shmem->tx_front);490491dev_vdbg(achan->acpm->dev, "ID = %d poll = %d, mlen = %d, qlen = %d\n",492achan->id, achan->poll_completion, achan->mlen, achan->qlen);493}494495/**496* acpm_achan_alloc_cmds() - allocate buffers for retrieving data from the ACPM497* firmware.498* @achan: ACPM channel info.499*500* Return: 0 on success, -errno otherwise.501*/502static int acpm_achan_alloc_cmds(struct acpm_chan *achan)503{504struct device *dev = achan->acpm->dev;505struct acpm_rx_data *rx_data;506size_t cmd_size, n_cmd;507int i;508509if (achan->mlen == 0)510return 0;511512cmd_size = sizeof(*(achan->rx_data[0].cmd));513n_cmd = DIV_ROUND_UP_ULL(achan->mlen, cmd_size);514515for (i = 0; i < ACPM_SEQNUM_MAX; i++) {516rx_data = &achan->rx_data[i];517rx_data->n_cmd = n_cmd;518rx_data->cmd = devm_kcalloc(dev, n_cmd, cmd_size, GFP_KERNEL);519if (!rx_data->cmd)520return -ENOMEM;521}522523return 0;524}525526/**527* acpm_free_mbox_chans() - free mailbox channels.528* @acpm: pointer to driver data.529*/530static void acpm_free_mbox_chans(struct acpm_info *acpm)531{532int i;533534for (i = 0; i < acpm->num_chans; i++)535if (!IS_ERR_OR_NULL(acpm->chans[i].chan))536mbox_free_channel(acpm->chans[i].chan);537}538539/**540* acpm_channels_init() - initialize channels based on the configuration data in541* the shared memory.542* @acpm: pointer to driver data.543*544* Return: 0 on success, -errno otherwise.545*/546static int acpm_channels_init(struct acpm_info *acpm)547{548struct acpm_shmem __iomem *shmem = acpm->shmem;549struct acpm_chan_shmem __iomem *chans_shmem;550struct device *dev = acpm->dev;551int i, ret;552553acpm->num_chans = readl(&shmem->num_chans);554acpm->chans = devm_kcalloc(dev, acpm->num_chans, sizeof(*acpm->chans),555GFP_KERNEL);556if (!acpm->chans)557return -ENOMEM;558559chans_shmem = acpm->sram_base + readl(&shmem->chans);560561for (i = 0; i < acpm->num_chans; i++) {562struct acpm_chan_shmem __iomem *chan_shmem = &chans_shmem[i];563struct acpm_chan *achan = &acpm->chans[i];564struct mbox_client *cl = &achan->cl;565566achan->acpm = acpm;567568acpm_chan_shmem_get_params(achan, chan_shmem);569570ret = acpm_achan_alloc_cmds(achan);571if (ret)572return ret;573574mutex_init(&achan->rx_lock);575mutex_init(&achan->tx_lock);576577cl->dev = dev;578579achan->chan = mbox_request_channel(cl, 0);580if (IS_ERR(achan->chan)) {581acpm_free_mbox_chans(acpm);582return PTR_ERR(achan->chan);583}584}585586return 0;587}588589/**590* acpm_setup_ops() - setup the operations structures.591* @acpm: pointer to the driver data.592*/593static void acpm_setup_ops(struct acpm_info *acpm)594{595struct acpm_dvfs_ops *dvfs_ops = &acpm->handle.ops.dvfs_ops;596struct acpm_pmic_ops *pmic_ops = &acpm->handle.ops.pmic_ops;597598dvfs_ops->set_rate = acpm_dvfs_set_rate;599dvfs_ops->get_rate = acpm_dvfs_get_rate;600601pmic_ops->read_reg = acpm_pmic_read_reg;602pmic_ops->bulk_read = acpm_pmic_bulk_read;603pmic_ops->write_reg = acpm_pmic_write_reg;604pmic_ops->bulk_write = acpm_pmic_bulk_write;605pmic_ops->update_reg = acpm_pmic_update_reg;606}607608static void acpm_clk_pdev_unregister(void *data)609{610platform_device_unregister(data);611}612613static int acpm_probe(struct platform_device *pdev)614{615const struct acpm_match_data *match_data;616struct platform_device *acpm_clk_pdev;617struct device *dev = &pdev->dev;618struct device_node *shmem;619struct acpm_info *acpm;620resource_size_t size;621struct resource res;622int ret;623624acpm = devm_kzalloc(dev, sizeof(*acpm), GFP_KERNEL);625if (!acpm)626return -ENOMEM;627628shmem = of_parse_phandle(dev->of_node, "shmem", 0);629ret = of_address_to_resource(shmem, 0, &res);630of_node_put(shmem);631if (ret)632return dev_err_probe(dev, ret,633"Failed to get shared memory.\n");634635size = resource_size(&res);636acpm->sram_base = devm_ioremap(dev, res.start, size);637if (!acpm->sram_base)638return dev_err_probe(dev, -ENOMEM,639"Failed to ioremap shared memory.\n");640641match_data = of_device_get_match_data(dev);642if (!match_data)643return dev_err_probe(dev, -EINVAL,644"Failed to get match data.\n");645646acpm->shmem = acpm->sram_base + match_data->initdata_base;647acpm->dev = dev;648649ret = acpm_channels_init(acpm);650if (ret)651return ret;652653acpm_setup_ops(acpm);654655platform_set_drvdata(pdev, acpm);656657acpm_clk_pdev = platform_device_register_data(dev,658match_data->acpm_clk_dev_name,659PLATFORM_DEVID_NONE, NULL, 0);660if (IS_ERR(acpm_clk_pdev))661return dev_err_probe(dev, PTR_ERR(acpm_clk_pdev),662"Failed to register ACPM clocks device.\n");663664ret = devm_add_action_or_reset(dev, acpm_clk_pdev_unregister,665acpm_clk_pdev);666if (ret)667return dev_err_probe(dev, ret, "Failed to add devm action.\n");668669return devm_of_platform_populate(dev);670}671672/**673* acpm_handle_put() - release the handle acquired by acpm_get_by_phandle.674* @handle: Handle acquired by acpm_get_by_phandle.675*/676static void acpm_handle_put(const struct acpm_handle *handle)677{678struct acpm_info *acpm = handle_to_acpm_info(handle);679struct device *dev = acpm->dev;680681module_put(dev->driver->owner);682/* Drop reference taken with of_find_device_by_node(). */683put_device(dev);684}685686/**687* devm_acpm_release() - devres release method.688* @dev: pointer to device.689* @res: pointer to resource.690*/691static void devm_acpm_release(struct device *dev, void *res)692{693acpm_handle_put(*(struct acpm_handle **)res);694}695696/**697* acpm_get_by_node() - get the ACPM handle using node pointer.698* @dev: device pointer requesting ACPM handle.699* @np: ACPM device tree node.700*701* Return: pointer to handle on success, ERR_PTR(-errno) otherwise.702*/703static const struct acpm_handle *acpm_get_by_node(struct device *dev,704struct device_node *np)705{706struct platform_device *pdev;707struct device_link *link;708struct acpm_info *acpm;709710pdev = of_find_device_by_node(np);711if (!pdev)712return ERR_PTR(-EPROBE_DEFER);713714acpm = platform_get_drvdata(pdev);715if (!acpm) {716platform_device_put(pdev);717return ERR_PTR(-EPROBE_DEFER);718}719720if (!try_module_get(pdev->dev.driver->owner)) {721platform_device_put(pdev);722return ERR_PTR(-EPROBE_DEFER);723}724725link = device_link_add(dev, &pdev->dev, DL_FLAG_AUTOREMOVE_SUPPLIER);726if (!link) {727dev_err(&pdev->dev,728"Failed to create device link to consumer %s.\n",729dev_name(dev));730platform_device_put(pdev);731module_put(pdev->dev.driver->owner);732return ERR_PTR(-EINVAL);733}734735return &acpm->handle;736}737738/**739* devm_acpm_get_by_node() - managed get handle using node pointer.740* @dev: device pointer requesting ACPM handle.741* @np: ACPM device tree node.742*743* Return: pointer to handle on success, ERR_PTR(-errno) otherwise.744*/745const struct acpm_handle *devm_acpm_get_by_node(struct device *dev,746struct device_node *np)747{748const struct acpm_handle **ptr, *handle;749750ptr = devres_alloc(devm_acpm_release, sizeof(*ptr), GFP_KERNEL);751if (!ptr)752return ERR_PTR(-ENOMEM);753754handle = acpm_get_by_node(dev, np);755if (!IS_ERR(handle)) {756*ptr = handle;757devres_add(dev, ptr);758} else {759devres_free(ptr);760}761762return handle;763}764EXPORT_SYMBOL_GPL(devm_acpm_get_by_node);765766static const struct acpm_match_data acpm_gs101 = {767.initdata_base = ACPM_GS101_INITDATA_BASE,768.acpm_clk_dev_name = "gs101-acpm-clk",769};770771static const struct of_device_id acpm_match[] = {772{773.compatible = "google,gs101-acpm-ipc",774.data = &acpm_gs101,775},776{},777};778MODULE_DEVICE_TABLE(of, acpm_match);779780static struct platform_driver acpm_driver = {781.probe = acpm_probe,782.driver = {783.name = "exynos-acpm-protocol",784.of_match_table = acpm_match,785},786};787module_platform_driver(acpm_driver);788789MODULE_AUTHOR("Tudor Ambarus <[email protected]>");790MODULE_DESCRIPTION("Samsung Exynos ACPM mailbox protocol driver");791MODULE_LICENSE("GPL");792793794